snapraid/cmdline/mingw.c
Andrea Mazzoleni 2bac137218 Remove the requirement to snapshot content files
The new content file format now includes metadata for previous versions
of files. This allows the fix command to access historical file data from
snapshots as needed, ensuring recovery even if the live filesystem has
changed or the file was deleted during a sync.
2026-06-03 22:00:59 +02:00

3528 lines
84 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni
#include "portable.h"
#ifdef __MINGW32__ /* Only for MingW */
#include "support.h"
/**
* Exit codes.
*/
int exit_success = 0;
int exit_failure = 1;
int exit_sync_needed = 2;
/* Add missing Windows declaration */
/* For SetThreadExecutionState */
#ifndef WIN32_ES_SYSTEM_REQUIRED
#define WIN32_ES_SYSTEM_REQUIRED 0x00000001L
#endif
#ifndef WIN32_ES_DISPLAY_REQUIRED
#define WIN32_ES_DISPLAY_REQUIRED 0x00000002L
#endif
#ifndef WIN32_ES_USER_PRESENT
#define WIN32_ES_USER_PRESENT 0x00000004L
#endif
#ifndef WIN32_ES_AWAYMODE_REQUIRED
#define WIN32_ES_AWAYMODE_REQUIRED 0x00000040L
#endif
#ifndef WIN32_ES_CONTINUOUS
#define WIN32_ES_CONTINUOUS 0x80000000L
#endif
/* File Index */
#ifndef FILE_INVALID_FILE_ID
#define FILE_INVALID_FILE_ID ((ULONGLONG)-1LL)
#endif
/**
* Direct access to RtlGenRandom().
* This function is accessible only with LoadLibrary() and it's available from Windows XP.
*/
static BOOLEAN(WINAPI * ptr_RtlGenRandom)(PVOID, ULONG);
/**
* Description of the last error.
* It's stored in the thread local storage.
*/
static windows_key_t last_error;
/**
* Monotone os_tick counter
*/
static windows_mutex_t tick_lock;
static uint64_t tick_last;
/**
* If we are running in Wine.
*/
static int is_wine;
/**
* If we should use the legacy FindFirst/Next way to list directories.
*/
static int is_scan_winfind;
/**
* Loaded ADVAPI32.DLL.
*/
static HMODULE dll_advapi32;
/**
* Executable dir.
*
* Or empty or terminating with '\'.
*/
static WCHAR exedir[MAX_PATH];
/**
* Set the executable dir.
*/
static void exedir_init(void)
{
DWORD size;
WCHAR* slash;
size = GetModuleFileNameW(0, exedir, MAX_PATH);
if (size == 0 || size == MAX_PATH) {
/* use empty dir */
exedir[0] = 0;
return;
}
slash = wcsrchr(exedir, L'\\');
if (!slash) {
/* use empty dir */
exedir[0] = 0;
return;
}
/* cut exe name */
slash[1] = 0;
}
void os_default_conf(char* conf, size_t conf_size, const char* argv0)
{
char* slash;
pathimport(conf, conf_size, argv0);
slash = strrchr(conf, '/');
if (slash) {
slash[1] = 0;
pathcat(conf, conf_size, PACKAGE ".conf");
} else {
pathcpy(conf, conf_size, PACKAGE ".conf");
}
}
void os_init(int opt)
{
HMODULE ntdll, kernel32;
is_scan_winfind = opt != 0;
/* initialize the thread local storage for strerror(), using free() as destructor */
if (windows_key_create(&last_error, free) != 0) {
log_fatal(EEXTERNAL, "Error calling windows_key_create().\n");
exit(EXIT_FAILURE);
}
tick_last = 0;
if (windows_mutex_init(&tick_lock, 0) != 0) {
log_fatal(EEXTERNAL, "Error calling windows_mutex_init().\n");
exit(EXIT_FAILURE);
}
ntdll = GetModuleHandle("NTDLL.DLL");
if (!ntdll) {
log_fatal(EEXTERNAL, "Error loading the NTDLL module.\n");
exit(EXIT_FAILURE);
}
kernel32 = GetModuleHandle("KERNEL32.DLL");
if (!kernel32) {
log_fatal(EEXTERNAL, "Error loading the KERNEL32 module.\n");
exit(EXIT_FAILURE);
}
dll_advapi32 = LoadLibrary("ADVAPI32.DLL");
if (!dll_advapi32) {
log_fatal(EEXTERNAL, "Error loading the ADVAPI32 module.\n");
exit(EXIT_FAILURE);
}
/* check for Wine presence */
is_wine = GetProcAddress(ntdll, "wine_get_version") != 0;
/* get pointer to RtlGenRandom, note that it was reported missing in some cases */
ptr_RtlGenRandom = (void*)GetProcAddress(dll_advapi32, "SystemFunction036");
/*
* Set the thread execution level to avoid sleep
* first try for Windows 7
*/
if (SetThreadExecutionState(WIN32_ES_CONTINUOUS | WIN32_ES_SYSTEM_REQUIRED | WIN32_ES_AWAYMODE_REQUIRED) == 0) {
/* retry with the XP variant */
SetThreadExecutionState(WIN32_ES_CONTINUOUS | WIN32_ES_SYSTEM_REQUIRED);
}
exedir_init();
/*
* Set LC_ALL=C to make child ignoring the locale when printing info
*
* Note anyway that in Windows this trick doesn't work because the
* Windows locale is used with priority.
*/
_wputenv_s(L"LC_ALL", L"C");
}
void os_done(void)
{
/* delete the thread local storage for strerror() */
windows_key_delete(last_error);
windows_mutex_destroy(&tick_lock);
/* restore the normal execution level */
SetThreadExecutionState(WIN32_ES_CONTINUOUS);
FreeLibrary(dll_advapi32);
}
void os_abort(void)
{
void* stack[32];
size_t size;
unsigned i;
printf("Stacktrace of " PACKAGE " v" VERSION);
printf(", mingw");
#ifdef __GNUC__
printf(", gcc " __VERSION__);
#endif
printf(", %d-bit", (int)sizeof(void*) * 8);
printf(", PATH_MAX=%d", PATH_MAX);
printf("\n");
/* get stackstrace, but without symbols */
size = CaptureStackBackTrace(0, 32, stack, NULL);
for (i = 0; i < size; ++i)
printf("[bt] %02u: %p\n", i, stack[i]);
printf("Please report this error to the SnapRAID Forum:\n");
printf("https://sourceforge.net/p/snapraid/discussion/1677233/\n");
/* use exit() and not abort to avoid the Windows abort dialog */
exit(EXIT_FAILURE);
}
void os_clear(void)
{
HANDLE console;
CONSOLE_SCREEN_BUFFER_INFO screen;
COORD coord;
DWORD written;
/* get the console */
console = GetStdHandle(STD_OUTPUT_HANDLE);
if (console == INVALID_HANDLE_VALUE)
return;
/* get the screen size */
if (!GetConsoleScreenBufferInfo(console, &screen))
return;
/* fill the screen with spaces */
coord.X = 0;
coord.Y = 0;
FillConsoleOutputCharacterA(console, ' ', screen.dwSize.X * screen.dwSize.Y, coord, &written);
/* set the cursor at the top left corner */
SetConsoleCursorPosition(console, coord);
}
/**
* Size in chars of conversion buffers for u8to16() and u16to8().
*/
#define CONV_MAX PATH_MAX
/**
* Convert a generic string from UTF8 to UTF16.
*/
static wchar_t* u8tou16(wchar_t* conv_buf, const char* src)
{
/* flags at 0 forces the API to never fail on malformed UTF-8 sequences */
int ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, conv_buf, CONV_MAX);
if (ret <= 0) {
DWORD error = GetLastError();
if (error == ERROR_INSUFFICIENT_BUFFER) {
log_fatal(EINTERNAL, "Path too long converting '%s' from UTF-8 to UTF-16\n", src);
} else {
log_fatal(EINTERNAL, "Error %u converting '%s' from UTF-8 to UTF-16\n", (unsigned)error, src);
}
os_abort();
}
return conv_buf;
}
/**
* Convert a generic string from UTF16 to UTF8.
*/
static char* u16tou8ex_may_fail(char* conv_buf, const wchar_t* src, size_t number_of_wchar, size_t* result_length_without_terminator)
{
/* WC_ERR_INVALID_CHARS forces the API to fail on malformed UTF-16 sequences */
int ret = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, number_of_wchar, conv_buf, CONV_MAX, 0, 0);
if (ret <= 0)
return 0;
*result_length_without_terminator = ret;
return conv_buf;
}
static char* u16tou8ex(char* conv_buf, const wchar_t* src, size_t number_of_wchar, size_t* result_length_without_terminator)
{
/* flags at 0 forces the API to never fail on malformed UTF-16 sequences */
int ret = WideCharToMultiByte(CP_UTF8, 0, src, number_of_wchar, conv_buf, CONV_MAX, 0, 0);
if (ret <= 0) {
DWORD error = GetLastError();
if (error == ERROR_INSUFFICIENT_BUFFER) {
log_fatal(EINTERNAL, "Path too long converting from UTF-16 to UTF-8 with len %u\n", (unsigned)number_of_wchar);
} else {
log_fatal(EINTERNAL, "Error %u converting from UTF-16 to UTF-8 with len %u\n", (unsigned)error, (unsigned)number_of_wchar);
if (src != 0) {
for (size_t i = 0; i < number_of_wchar; ++i) {
log_fatal(EINTERNAL, "%4u: %04x\n", (unsigned)i, src[i]);
}
}
}
os_abort();
}
*result_length_without_terminator = ret;
return conv_buf;
}
static char* u16tou8(char* conv_buf, const wchar_t* src)
{
size_t len;
/* convert also the 0 terminator */
return u16tou8ex(conv_buf, src, wcslen(src) + 1, &len);
}
/**
* Check if the char is a forward or back slash.
*/
static int is_slash(char c)
{
return c == '/' || c == '\\';
}
/**
* Convert a path to the Windows format.
*
* If only_is_required is 1, the extended-length format is used only if required.
*
* The exact operation done is:
* - If it's a '\\?\' or '\\.\' path, convert any '/' to '\'.
* - If it's a disk designator path, like 'D:\' or 'D:/', it prepends '\\?\' to the path and convert any '/' to '\'.
* - If it's a UNC path, like ''\\server'', it prepends '\\?\UNC\' to the path and convert any '/' to '\'.
* - Otherwise, only the UTF conversion is done. In this case Windows imposes a limit of 260 chars, and automatically convert any '/' to '\'.
*
* For more details see:
* Naming Files, Paths, and Namespaces
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath
*/
static wchar_t* convert_arg(wchar_t* conv_buf, const char* src, int only_if_required)
{
int ret;
wchar_t* dst;
int count;
dst = conv_buf;
/*
* Note that we always check for both / and \ because the path is blindly
* converted to unix format by path_import()
*/
if (only_if_required && strlen(src) < 260 - 12) {
/*
* It's a short path
* 260 is the MAX_PATH, note that it includes the space for the terminating NUL
* 12 is an additional space for filename, required when creating directory
*/
/* do nothing */
} else if (is_slash(src[0]) && is_slash(src[1]) && (src[2] == '?' || src[2] == '.') && is_slash(src[3])) {
/* if it's already a '\\?\' or '\\.\' path */
/* do nothing */
} else if (is_slash(src[0]) && is_slash(src[1])) {
/* if it is a UNC path, like '\\server' */
/* prefix with '\\?\UNC\' */
*dst++ = L'\\';
*dst++ = L'\\';
*dst++ = L'?';
*dst++ = L'\\';
*dst++ = L'U';
*dst++ = L'N';
*dst++ = L'C';
*dst++ = L'\\';
/* skip initial '\\' */
src += 2;
} else if (src[0] != 0 && src[1] == ':' && is_slash(src[2])) {
/* if it is a disk designator path, like 'D:\' or 'D:/' */
/* prefix with '\\?\' */
*dst++ = L'\\';
*dst++ = L'\\';
*dst++ = L'?';
*dst++ = L'\\';
}
/* chars already used */
count = dst - conv_buf;
ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, CONV_MAX - count);
if (ret <= 0) {
DWORD error = GetLastError();
if (error == ERROR_INSUFFICIENT_BUFFER) {
log_fatal(EINTERNAL, "Path too long converting '%s' from UTF-8 to UTF-16\n", src);
} else {
log_fatal(EINTERNAL, "Error %u converting '%s' from UTF-8 to UTF-16\n", (unsigned)error, src);
}
os_abort();
}
/*
* Convert any / to \
* note that in UTF-16, it's not possible to have '/' used as part
* of a pair of codes representing a single UNICODE char
* See: http://en.wikipedia.org/wiki/UTF-16
*/
while (*dst) {
if (*dst == L'/')
*dst = L'\\';
++dst;
}
return conv_buf;
}
#define convert(buf, a) convert_arg(buf, a, 0)
#define convert_if_required(buf, a) convert_arg(buf, a, 1)
static BOOL GetReparseTagInfoByHandle(HANDLE hFile, FILE_ATTRIBUTE_TAG_INFO* lpFileAttributeTagInfo, DWORD dwFileAttributes)
{
/* if not a reparse point, return no info */
if ((dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
lpFileAttributeTagInfo->FileAttributes = dwFileAttributes;
lpFileAttributeTagInfo->ReparseTag = 0;
return TRUE;
}
/* do the real call */
return GetFileInformationByHandleEx(hFile, FileAttributeTagInfo, lpFileAttributeTagInfo, sizeof(FILE_ATTRIBUTE_TAG_INFO));
}
/**
* Convert Windows attr to the Unix stat format.
*/
static void windows_attr2stat(DWORD FileAttributes, DWORD ReparseTag, struct windows_stat* st)
{
/* Convert special attributes */
if ((FileAttributes & FILE_ATTRIBUTE_DEVICE) != 0) {
st->st_mode = S_IFBLK;
st->st_desc = "device";
} else if ((FileAttributes & FILE_ATTRIBUTE_OFFLINE) != 0) { /* Offline */
st->st_mode = S_IFCHR;
st->st_desc = "offline";
} else if ((FileAttributes & FILE_ATTRIBUTE_TEMPORARY) != 0) { /* Temporary */
st->st_mode = S_IFCHR;
st->st_desc = "temporary";
} else if ((FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { /* Reparse point */
switch (ReparseTag) {
/* if we don't have the ReparseTag information */
case 0 :
/* don't set the st_mode, to set it later calling lstat_sync() */
st->st_mode = 0;
st->st_desc = "unknown";
break;
/* for deduplicated files, assume that they are regular ones */
case IO_REPARSE_TAG_DEDUP :
if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
st->st_mode = S_IFDIR;
st->st_desc = "directory-dedup";
} else {
st->st_mode = S_IFREG;
st->st_desc = "regular-dedup";
}
break;
case IO_REPARSE_TAG_SYMLINK :
if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
st->st_mode = S_IFLNKDIR;
st->st_desc = "reparse-point-symlink-dir";
} else {
st->st_mode = S_IFLNK;
st->st_desc = "reparse-point-symlink-file";
}
break;
/* all the other are skipped as reparse-point */
case IO_REPARSE_TAG_MOUNT_POINT :
st->st_mode = S_IFCHR;
st->st_desc = "reparse-point-mount";
break;
case IO_REPARSE_TAG_NFS :
st->st_mode = S_IFCHR;
st->st_desc = "reparse-point-nfs";
break;
default :
st->st_mode = S_IFCHR;
st->st_desc = "reparse-point";
break;
}
} else if ((FileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0) { /* System */
if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
st->st_mode = S_IFCHR;
st->st_desc = "system-directory";
} else {
st->st_mode = S_IFREG;
st->st_desc = "system-file";
}
} else {
if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
st->st_mode = S_IFDIR;
st->st_desc = "directory";
} else {
st->st_mode = S_IFREG;
st->st_desc = "regular";
}
}
/* store the HIDDEN attribute in a separate field */
st->st_hidden = (FileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0;
}
/**
* Convert Windows info to the Unix stat format.
*/
static int windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ATTRIBUTE_TAG_INFO* tag, struct windows_stat* st)
{
int64_t mtime;
windows_attr2stat(info->dwFileAttributes, tag->ReparseTag, st);
st->st_size = info->nFileSizeHigh;
st->st_size <<= 32;
st->st_size |= info->nFileSizeLow;
mtime = info->ftLastWriteTime.dwHighDateTime;
mtime <<= 32;
mtime |= info->ftLastWriteTime.dwLowDateTime;
/*
* Convert to unix time
*
* How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
* http://support.microsoft.com/kb/167296
*/
mtime -= 116444736000000000LL;
st->st_mtime = mtime / 10000000;
st->st_mtimensec = (mtime % 10000000) * 100;
st->st_ino = info->nFileIndexHigh;
st->st_ino <<= 32;
st->st_ino |= info->nFileIndexLow;
st->st_nlink = info->nNumberOfLinks;
st->st_dev = info->dwVolumeSerialNumber;
/* GetFileInformationByHandle() ensures to return synced information */
st->st_sync = 1;
/**
* In ReFS the IDs are 128 bit, and the 64 bit interface may fail.
*
* From Microsoft "Application Compatibility with ReFS"
* http://download.microsoft.com/download/C/B/3/CB3561DC-6BF6-443D-B5B9-9676ACDF7F75/Application%20Compatibility%20with%20ReFS.docx
* "64-bit file identifier can be obtained from GetFileInformationByHandle in"
* "the nFileIndexHigh and nFileIndexLow members. This API is an extended version"
* "that includes 128-bit file identifiers. If GetFileInformationByHandle returns"
* "FILE_INVALID_FILE_ID, the identifier may only be described in 128 bit form."
*/
if (st->st_ino == (uint64_t)FILE_INVALID_FILE_ID) {
errno = EINVAL;
log_fatal(errno, "Invalid inode number! Is this ReFS?\n");
return -1;
}
return 0;
}
/**
* Convert Windows info to the Unix stat format.
*/
static int windows_stream2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ID_BOTH_DIR_INFO* stream, struct windows_stat* st)
{
int64_t mtime;
/*
* The FILE_ID_BOTH_DIR_INFO doesn't have the ReparseTag information
* we could use instead FILE_ID_EXTD_DIR_INFO, but it's available only
* from Windows Server 2012
*/
windows_attr2stat(stream->FileAttributes, 0, st);
st->st_size = stream->EndOfFile.QuadPart;
mtime = stream->LastWriteTime.QuadPart;
/*
* Convert to unix time
*
* How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
* http://support.microsoft.com/kb/167296
*/
mtime -= 116444736000000000LL;
st->st_mtime = mtime / 10000000;
st->st_mtimensec = (mtime % 10000000) * 100;
st->st_ino = stream->FileId.QuadPart;
st->st_nlink = info->nNumberOfLinks;
st->st_dev = info->dwVolumeSerialNumber;
/* directory listing doesn't ensure to return synced information */
st->st_sync = 0;
/* in ReFS the IDs are 128 bit, and the 64 bit interface may fail */
if (st->st_ino == (uint64_t)FILE_INVALID_FILE_ID) {
errno = EINVAL;
log_fatal(errno, "Invalid inode number! Is this ReFS?\n");
return -1;
}
return 0;
}
/**
* Convert Windows findfirst info to the Unix stat format.
*/
static void windows_finddata2stat(const WIN32_FIND_DATAW* info, struct windows_stat* st)
{
int64_t mtime;
windows_attr2stat(info->dwFileAttributes, info->dwReserved0, st);
st->st_size = info->nFileSizeHigh;
st->st_size <<= 32;
st->st_size |= info->nFileSizeLow;
mtime = info->ftLastWriteTime.dwHighDateTime;
mtime <<= 32;
mtime |= info->ftLastWriteTime.dwLowDateTime;
/*
* Convert to unix time
*
* How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
* http://support.microsoft.com/kb/167296
*/
mtime -= 116444736000000000LL;
st->st_mtime = mtime / 10000000;
st->st_mtimensec = (mtime % 10000000) * 100;
/* No inode information available */
st->st_ino = 0;
/* No link information available */
st->st_nlink = 0;
/* No device information available */
st->st_dev = 0;
/* directory listing doesn't ensure to return synced information */
st->st_sync = 0;
}
static void windows_finddata2dirent(const WIN32_FIND_DATAW* info, struct windows_dirent* dirent)
{
char conv_buf[CONV_MAX];
const char* name;
size_t len;
name = u16tou8ex(conv_buf, info->cFileName, wcslen(info->cFileName), &len);
if (len + 1 >= sizeof(dirent->d_name)) {
log_fatal(EINTERNAL, "Name too long\n");
exit(EXIT_FAILURE);
}
memcpy(dirent->d_name, name, len);
dirent->d_name[len] = 0;
windows_finddata2stat(info, &dirent->d_stat);
}
static int windows_stream2dirent(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ID_BOTH_DIR_INFO* stream, struct windows_dirent* dirent)
{
char conv_buf[CONV_MAX];
const char* name;
size_t len;
name = u16tou8ex(conv_buf, stream->FileName, stream->FileNameLength / 2, &len);
if (len + 1 >= sizeof(dirent->d_name)) {
log_fatal(EINTERNAL, "Name too long\n");
exit(EXIT_FAILURE);
}
memcpy(dirent->d_name, name, len);
dirent->d_name[len] = 0;
return windows_stream2stat(info, stream, &dirent->d_stat);
}
/**
* Convert Windows error to errno.
*/
static void windows_errno(DWORD error)
{
switch (error) {
case ERROR_INVALID_HANDLE :
/*
* We check for a bad handle calling _get_osfhandle()
* and in such case we return EBADF
* Other cases are here identified with EINVAL
*/
errno = EINVAL;
break;
case ERROR_HANDLE_EOF : /* in ReadFile() over the end of the file */
errno = EINVAL;
break;
case ERROR_FILE_NOT_FOUND :
case ERROR_PATH_NOT_FOUND : /* in GetFileAttributeW() if internal path not found */
errno = ENOENT;
break;
case ERROR_ACCESS_DENIED : /* in CreateDirectoryW() if dir is scheduled for deletion */
case ERROR_CURRENT_DIRECTORY : /* in RemoveDirectoryW() if removing the current directory */
case ERROR_SHARING_VIOLATION : /* in RemoveDirectoryW() if in use */
case ERROR_WRITE_PROTECT : /* when dealing with read-only media/snapshot and trying to write to them */
errno = EACCES;
break;
case ERROR_ALREADY_EXISTS : /* in CreateDirectoryW() if already exists */
errno = EEXIST;
break;
case ERROR_DISK_FULL :
errno = ENOSPC;
break;
case ERROR_BUFFER_OVERFLOW :
errno = ENAMETOOLONG;
break;
case ERROR_NOT_ENOUGH_MEMORY :
errno = ENOMEM;
break;
case ERROR_NOT_SUPPORTED : /* in CreateSymlinkW() if not present in kernel32 */
errno = ENOSYS;
break;
case ERROR_PRIVILEGE_NOT_HELD : /* in CreateSymlinkW() if no SeCreateSymbolicLinkPrivilige permission */
errno = EPERM;
break;
case ERROR_IO_DEVICE : /* in ReadFile() and WriteFile() */
case ERROR_CRC : /* in ReadFile() */
errno = EIO;
break;
default :
errno = ENXIO;
log_fatal(errno, "Unexpected Windows error %lu.\n", error);
break;
}
}
int windows_fstat(int fd, struct windows_stat* st)
{
BY_HANDLE_FILE_INFORMATION info;
FILE_ATTRIBUTE_TAG_INFO tag;
HANDLE h;
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
if (!GetFileInformationByHandle(h, &info)) {
windows_errno(GetLastError());
return -1;
}
if (!GetReparseTagInfoByHandle(h, &tag, info.dwFileAttributes)) {
windows_errno(GetLastError());
return -1;
}
return windows_info2stat(&info, &tag, st);
}
int windows_lstat(const char* file, struct windows_stat* st)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
WIN32_FIND_DATAW data;
/* FindFirstFileW by default gets information of symbolic links and not of their targets */
h = FindFirstFileW(convert(conv_buf, file), &data);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
if (!FindClose(h)) {
windows_errno(GetLastError());
return -1;
}
windows_finddata2stat(&data, st);
return 0;
}
void windows_dirent_lstat(const struct windows_dirent* dd, struct windows_stat* st)
{
memcpy(st, &dd->d_stat, sizeof(struct windows_stat));
}
int windows_mkdir(const char* file)
{
wchar_t conv_buf[CONV_MAX];
if (!CreateDirectoryW(convert(conv_buf, file), 0)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
int windows_rmdir(const char* file)
{
wchar_t conv_buf[CONV_MAX];
if (!RemoveDirectoryW(convert(conv_buf, file))) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
static BOOL GetFilePhysicalOffset(HANDLE h, uint64_t* physical)
{
STARTING_VCN_INPUT_BUFFER svib;
unsigned char rpb_buffer[sizeof(RETRIEVAL_POINTERS_BUFFER)];
RETRIEVAL_POINTERS_BUFFER* rpb = (RETRIEVAL_POINTERS_BUFFER*)&rpb_buffer;
BOOL ret;
DWORD n;
/* in Wine FSCTL_GET_RETRIEVAL_POINTERS is not supported */
if (is_wine) {
*physical = FILEPHY_UNREPORTED_OFFSET;
return TRUE;
}
/* set the output variable, just to be safe */
rpb->ExtentCount = 0;
/* read the physical address */
svib.StartingVcn.QuadPart = 0;
ret = DeviceIoControl(h, FSCTL_GET_RETRIEVAL_POINTERS, &svib, sizeof(svib), rpb_buffer, sizeof(rpb_buffer), &n, 0);
if (!ret) {
DWORD error = GetLastError();
if (error == ERROR_MORE_DATA) {
/*
* We ignore ERROR_MODE_DATA because we are interested only at the first entry
* and this is the expected error if the files has more entries
*/
} else if (error == ERROR_HANDLE_EOF) {
/*
* If the file is small, it can be stored in the Master File Table (MFT)
* and then it doesn't have a physical address
* In such case we report a specific fake address, to report this special condition
* that it's different from the 0 offset reported by the underline file system
*/
*physical = FILEPHY_WITHOUT_OFFSET;
return TRUE;
} else if (error == ERROR_NOT_SUPPORTED) {
/* for disks shared on network this operation is not supported */
*physical = FILEPHY_UNREPORTED_OFFSET;
return TRUE;
} else {
return FALSE;
}
}
if (rpb->ExtentCount < 1)
*physical = FILEPHY_UNREPORTED_OFFSET;
else
*physical = rpb->Extents[0].Lcn.QuadPart + FILEPHY_REAL_OFFSET;
return TRUE;
}
int lstat_sync(const char* file, struct windows_stat* st, uint64_t* physical)
{
wchar_t conv_buf[CONV_MAX];
BY_HANDLE_FILE_INFORMATION info;
FILE_ATTRIBUTE_TAG_INFO tag;
HANDLE h;
/*
* Open the handle of the file.
*
* Use FILE_FLAG_BACKUP_SEMANTICS to open directories (it's just ignored for files).
* Use FILE_FLAG_OPEN_REPARSE_POINT to open symbolic links and not the their target.
*
* Note that even with FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
* and FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT some paths
* cannot be opened like "C:\System Volume Information" resulting
* in error ERROR_ACCESS_DENIED.
*/
h = CreateFileW(convert(conv_buf, file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
if (!GetFileInformationByHandle(h, &info)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if (!GetReparseTagInfoByHandle(h, &tag, info.dwFileAttributes)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
/* read the physical offset, only if a pointer is provided */
if (physical != 0) {
if (!GetFilePhysicalOffset(h, physical)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
}
if (!CloseHandle(h)) {
windows_errno(GetLastError());
return -1;
}
return windows_info2stat(&info, &tag, st);
}
int windows_stat(const char* file, struct windows_stat* st)
{
wchar_t conv_buf[CONV_MAX];
BY_HANDLE_FILE_INFORMATION info;
FILE_ATTRIBUTE_TAG_INFO tag;
HANDLE h;
/*
* Open the handle of the file.
*
* Use FILE_FLAG_BACKUP_SEMANTICS to open directories (it's just ignored for files).
*/
h = CreateFileW(convert(conv_buf, file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
if (!GetFileInformationByHandle(h, &info)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if (!GetReparseTagInfoByHandle(h, &tag, info.dwFileAttributes)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if (!CloseHandle(h)) {
windows_errno(GetLastError());
return -1;
}
return windows_info2stat(&info, &tag, st);
}
int windows_ftruncate(int fd, off64_t off)
{
HANDLE h;
LARGE_INTEGER pos;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
pos.QuadPart = off;
if (!SetFilePointerEx(h, pos, 0, FILE_BEGIN)) {
windows_errno(GetLastError());
return -1;
}
/*
* Windows effectively reserves space, but it doesn't initialize it.
* It's then important to write starting from the begin to the end,
* to avoid to have Windows to fill the holes writing zeros.
*
* See:
* "Why does my single-byte write take forever?"
* http://blogs.msdn.com/b/oldnewthing/archive/2011/09/22/10215053.aspx
*/
if (!SetEndOfFile(h)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
int windows_fallocate(int fd, int mode, off64_t off, off64_t len)
{
if (mode != 0)
return -1;
/* no difference with ftruncate because Windows doesn't use sparse files */
return windows_ftruncate(fd, off + len);
}
int windows_fsync(int fd)
{
HANDLE h;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
/*
* "The FlushFileBuffers API can be used to flush all the outstanding data
* and metadata on a single file or a whole volume. However, frequent use
* of this API can cause reduced throughput. Internally, Windows uses the
* SCSI Synchronize Cache or the IDE/ATAPI Flush cache commands."
*
* From:
* "Windows Write Caching - Part 2 An overview for Application Developers"
* http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/
*/
if (!FlushFileBuffers(h)) {
DWORD error = GetLastError();
switch (error) {
case ERROR_INVALID_HANDLE :
/*
* FlushFileBuffers returns this error if the handle
* doesn't support buffering, like the console output.
*
* We had a report that also ATA-over-Ethernet returns
* this error, but not enough sure to ignore it.
* So, we use now an extended error reporting.
*/
windows_errno(error);
log_fatal(errno, "Unexpected Windows INVALID_HANDLE error in FlushFileBuffers().\n");
log_fatal(errno, "Are you using ATA-over-Ethernet ? Please report it.\n");
return -1;
case ERROR_ACCESS_DENIED :
/*
* FlushFileBuffers returns this error for read-only
* data, that cannot have to be flushed.
*/
return 0;
default :
windows_errno(error);
return -1;
}
}
return 0;
}
static int windows_vsync(const wchar_t* volume)
{
HANDLE h;
DWORD bytes;
/* open the volume (volumeName already in \\?\Volume{GUID} format) */
h = CreateFileW(volume, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
/*
* "The FlushFileBuffers API can be used to flush all the outstanding data
* and metadata on a single file or a whole volume. However, frequent use
* of this API can cause reduced throughput. Internally, Windows uses the
* SCSI Synchronize Cache or the IDE/ATAPI Flush cache commands."
*
* From:
* "Windows Write Caching - Part 2 An overview for Application Developers"
* http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/
*/
if (!FlushFileBuffers(h)) {
DWORD error = GetLastError();
CloseHandle(h);
switch (error) {
case ERROR_INVALID_HANDLE :
/*
* FlushFileBuffers returns this error if the handle
* doesn't support buffering, like the console output.
*
* We had a report that also ATA-over-Ethernet returns
* this error, but not enough sure to ignore it.
* So, we use now an extended error reporting.
*/
windows_errno(error);
log_fatal(errno, "Unexpected Windows INVALID_HANDLE error in FlushFileBuffers().\n");
log_fatal(errno, "Are you using ATA-over-Ethernet ? Please report it.\n");
return -1;
case ERROR_ACCESS_DENIED :
/*
* FlushFileBuffers returns this error for read-only
* data, that cannot have to be flushed.
*/
return 0;
default :
windows_errno(error);
return -1;
}
}
/* lock the volume (may fail if volume is in use, which is fine) */
if (!DeviceIoControl(h, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytes, NULL)) {
DWORD error = GetLastError();
CloseHandle(h);
switch (error) {
case ERROR_ACCESS_DENIED :
/*
* ERROR_ACCESS_DENIED (5) means volume is in use - this is expected and OK
*/
return 0;
default :
windows_errno(error);
return -1;
}
}
/* unlock immediately - the lock/unlock cycle helps ensure consistency */
if (!DeviceIoControl(h, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytes, NULL)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
if (!CloseHandle(h)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
static int windows_async(void)
{
HANDLE h;
wchar_t volume[MAX_PATH];
DWORD error = 0;
DWORD count = 0;
DWORD success = 0;
h = FindFirstVolumeW(volume, sizeof(volume) / sizeof(wchar_t));
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
do {
/* remove trailing backslash */
size_t len = wcslen(volume);
if (len > 0 && volume[len - 1] == L'\\')
volume[len - 1] = L'\0';
++count;
if (windows_vsync(volume) == 0) {
++success;
} else {
/* save the first error */
if (error == 0)
error = GetLastError();
}
} while (FindNextVolumeW(h, volume, sizeof(volume) / sizeof(wchar_t)));
/* if at least one failed */
if (count != success) {
FindVolumeClose(h);
windows_errno(error);
return -1;
}
error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
FindVolumeClose(h);
windows_errno(error);
return -1;
}
if (!FindVolumeClose(h)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
int windows_sync(void)
{
if (windows_async() != 0)
return -1;
return 0;
}
int windows_futimens(int fd, struct windows_timespec tv[2])
{
HANDLE h;
FILETIME ft;
int64_t mtime;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
/*
* Convert to windows time
*
* How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
* http://support.microsoft.com/kb/167296
*/
mtime = tv[0].tv_sec;
mtime *= 10000000;
mtime += tv[0].tv_nsec / 100;
mtime += 116444736000000000LL;
ft.dwHighDateTime = mtime >> 32;
ft.dwLowDateTime = mtime;
if (!SetFileTime(h, 0, 0, &ft)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
int windows_utimensat(int fd, const char* file, struct windows_timespec tv[2], int flags)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
FILETIME ft;
int64_t mtime;
DWORD wflags;
/*
* Support only the absolute paths
*/
if (fd != AT_FDCWD) {
errno = EBADF;
return -1;
}
/*
* Open the handle of the file.
*
* Use FILE_FLAG_BACKUP_SEMANTICS to open directories (it's just ignored for files).
* Use FILE_FLAG_OPEN_REPARSE_POINT to open symbolic links and not the their target.
*
* Note that even with FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
* and FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT some paths
* cannot be opened like "C:\System Volume Information" resulting
* in error ERROR_ACCESS_DENIED.
*/
wflags = FILE_FLAG_BACKUP_SEMANTICS;
if ((flags & AT_SYMLINK_NOFOLLOW) != 0)
wflags |= FILE_FLAG_OPEN_REPARSE_POINT;
h = CreateFileW(convert(conv_buf, file), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, wflags, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
/*
* Convert to windows time
*
* How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME
* http://support.microsoft.com/kb/167296
*/
mtime = tv[0].tv_sec;
mtime *= 10000000;
mtime += tv[0].tv_nsec / 100;
mtime += 116444736000000000LL;
ft.dwHighDateTime = mtime >> 32;
ft.dwLowDateTime = mtime;
if (!SetFileTime(h, 0, 0, &ft)) {
windows_errno(GetLastError());
CloseHandle(h);
return -1;
}
if (!CloseHandle(h)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
int windows_rename(const char* from, const char* to)
{
wchar_t conv_buf_from[CONV_MAX];
wchar_t conv_buf_to[CONV_MAX];
/*
* Implements an atomic rename in Windows.
* Not really atomic at now to support XP.
*
* Is an atomic file rename (with overwrite) possible on Windows?
* http://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows
*/
if (!MoveFileExW(convert(conv_buf_from, from), convert(conv_buf_to, to), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
int windows_remove(const char* file)
{
wchar_t conv_buf[CONV_MAX];
if (!DeleteFileW(convert(conv_buf, file))) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
FILE* windows_fopen(const char* file, const char* mode)
{
wchar_t conv_buf_file[CONV_MAX];
wchar_t conv_buf_mode[CONV_MAX];
return _wfopen(convert(conv_buf_file, file), u8tou16(conv_buf_mode, mode));
}
int windows_open(const char* file, int flags, ...)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
int f;
DWORD access;
DWORD share;
DWORD create;
DWORD attr;
switch (flags & O_ACCMODE) {
case O_RDONLY :
access = GENERIC_READ;
break;
case O_WRONLY :
access = GENERIC_WRITE;
break;
case O_RDWR :
access = GENERIC_READ | GENERIC_WRITE;
break;
default :
errno = EINVAL;
return -1;
}
share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
switch (flags & (O_CREAT | O_EXCL | O_TRUNC)) {
case 0 :
create = OPEN_EXISTING;
break;
case O_CREAT :
create = OPEN_ALWAYS;
break;
case O_CREAT | O_EXCL :
case O_CREAT | O_EXCL | O_TRUNC :
create = CREATE_NEW;
break;
case O_CREAT | O_TRUNC :
create = CREATE_ALWAYS;
break;
case O_TRUNC :
create = TRUNCATE_EXISTING;
break;
default :
errno = EINVAL;
return -1;
}
attr = FILE_ATTRIBUTE_NORMAL;
if ((flags & O_DIRECT) != 0)
attr |= FILE_FLAG_NO_BUFFERING;
if ((flags & O_DSYNC) != 0)
attr |= FILE_FLAG_WRITE_THROUGH;
if ((flags & O_RANDOM) != 0)
attr |= FILE_FLAG_RANDOM_ACCESS;
if ((flags & O_SEQUENTIAL) != 0)
attr |= FILE_FLAG_SEQUENTIAL_SCAN;
if ((flags & _O_SHORT_LIVED) != 0)
attr |= FILE_ATTRIBUTE_TEMPORARY;
if ((flags & O_TEMPORARY) != 0)
attr |= FILE_FLAG_DELETE_ON_CLOSE;
h = CreateFileW(convert(conv_buf, file), access, share, 0, create, attr, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
/* mask out flags unknown by Windows */
flags &= ~(O_DIRECT | O_DSYNC);
f = _open_osfhandle((intptr_t)h, flags);
if (f == -1) {
CloseHandle(h);
return -1;
}
return f;
}
struct windows_dir_struct {
BY_HANDLE_FILE_INFORMATION info;
WIN32_FIND_DATAW find;
HANDLE h;
struct windows_dirent entry;
unsigned char* buffer;
unsigned buffer_size;
unsigned buffer_pos;
int state;
};
#define DIR_STATE_EOF -1 /**< End of the dir stream */
#define DIR_STATE_EMPTY 0 /**< The entry is empty. */
#define DIR_STATE_FILLED 1 /**< The entry is valid. */
static windows_dir* windows_opendir_find(const char* dir)
{
wchar_t conv_buf[CONV_MAX];
wchar_t* wdir;
windows_dir* dirstream;
size_t len;
dirstream = malloc(sizeof(windows_dir));
if (!dirstream) {
log_fatal(EFAULT, "Low memory\n");
exit(EXIT_FAILURE);
}
wdir = convert(conv_buf, dir);
/* add final / and * */
len = wcslen(wdir);
if (len != 0 && wdir[len - 1] != '\\')
wdir[len++] = L'\\';
wdir[len++] = L'*';
wdir[len++] = 0;
dirstream->h = FindFirstFileW(wdir, &dirstream->find);
if (dirstream->h == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) {
dirstream->state = DIR_STATE_EOF;
return dirstream;
}
free(dirstream);
windows_errno(error);
return 0;
}
windows_finddata2dirent(&dirstream->find, &dirstream->entry);
dirstream->state = DIR_STATE_FILLED;
return dirstream;
}
static struct windows_dirent* windows_readdir_find(windows_dir* dirstream)
{
if (dirstream->state == DIR_STATE_EMPTY) {
if (!FindNextFileW(dirstream->h, &dirstream->find)) {
DWORD error = GetLastError();
if (error != ERROR_NO_MORE_FILES) {
windows_errno(error);
return 0;
}
dirstream->state = DIR_STATE_EOF;
} else {
windows_finddata2dirent(&dirstream->find, &dirstream->entry);
dirstream->state = DIR_STATE_FILLED;
}
}
if (dirstream->state == DIR_STATE_FILLED) {
dirstream->state = DIR_STATE_EMPTY;
return &dirstream->entry;
}
/* otherwise it's the end of stream */
assert(dirstream->state == DIR_STATE_EOF);
errno = 0;
return 0;
}
static int windows_closedir_find(windows_dir* dirstream)
{
if (dirstream->h != INVALID_HANDLE_VALUE) {
if (!FindClose(dirstream->h)) {
DWORD error = GetLastError();
free(dirstream);
windows_errno(error);
return -1;
}
}
free(dirstream);
return 0;
}
static int windows_first_stream(windows_dir* dirstream)
{
FILE_ID_BOTH_DIR_INFO* fd;
if (!GetFileInformationByHandleEx(dirstream->h, FileIdBothDirectoryInfo, dirstream->buffer, dirstream->buffer_size)) {
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_FILES) {
dirstream->state = DIR_STATE_EOF;
return 0;
}
windows_errno(error);
return -1;
}
/* get the first entry */
dirstream->state = DIR_STATE_FILLED;
dirstream->buffer_pos = 0;
fd = (FILE_ID_BOTH_DIR_INFO*)dirstream->buffer;
return windows_stream2dirent(&dirstream->info, fd, &dirstream->entry);
}
static int windows_next_stream(windows_dir* dirstream)
{
FILE_ID_BOTH_DIR_INFO* fd;
/* last entry read */
fd = (FILE_ID_BOTH_DIR_INFO*)(dirstream->buffer + dirstream->buffer_pos);
/* check if there is a next one */
if (fd->NextEntryOffset == 0) {
/* if not, fill it up again */
if (windows_first_stream(dirstream) != 0)
return -1;
return 0;
}
/* go to the next one */
dirstream->state = DIR_STATE_FILLED;
dirstream->buffer_pos += fd->NextEntryOffset;
fd = (FILE_ID_BOTH_DIR_INFO*)(dirstream->buffer + dirstream->buffer_pos);
return windows_stream2dirent(&dirstream->info, fd, &dirstream->entry);
}
static windows_dir* windows_opendir_stream(const char* dir)
{
wchar_t conv_buf[CONV_MAX];
windows_dir* dirstream;
WCHAR* wdir;
dirstream = malloc(sizeof(windows_dir));
if (!dirstream) {
log_fatal(EFAULT, "Low memory\n");
exit(EXIT_FAILURE);
}
wdir = convert(conv_buf, dir);
/* uses a 64 kB buffer for reading directory */
dirstream->buffer_size = 64 * 1024;
dirstream->buffer = malloc(dirstream->buffer_size);
if (!dirstream->buffer) {
log_fatal(EFAULT, "Low memory\n");
exit(EXIT_FAILURE);
}
dirstream->h = CreateFileW(wdir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (dirstream->h == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
free(dirstream->buffer);
free(dirstream);
windows_errno(error);
return 0;
}
/*
* Get dir information for the VolumeSerialNumber
* this value is used for all the files in the dir
*/
if (!GetFileInformationByHandle(dirstream->h, &dirstream->info)) {
DWORD error = GetLastError();
CloseHandle(dirstream->h);
free(dirstream->buffer);
free(dirstream);
windows_errno(error);
return 0;
}
if (windows_first_stream(dirstream) != 0) {
CloseHandle(dirstream->h);
free(dirstream->buffer);
free(dirstream);
return 0;
}
return dirstream;
}
static struct windows_dirent* windows_readdir_stream(windows_dir* dirstream)
{
if (dirstream->state == DIR_STATE_EMPTY) {
if (windows_next_stream(dirstream) != 0)
return 0;
}
if (dirstream->state == DIR_STATE_FILLED) {
dirstream->state = DIR_STATE_EMPTY;
return &dirstream->entry;
}
/* otherwise it's the end of stream */
assert(dirstream->state == DIR_STATE_EOF);
errno = 0;
return 0;
}
static int windows_closedir_stream(windows_dir* dirstream)
{
if (dirstream->h != INVALID_HANDLE_VALUE) {
if (!CloseHandle(dirstream->h)) {
DWORD error = GetLastError();
free(dirstream->buffer);
free(dirstream);
windows_errno(error);
return -1;
}
}
free(dirstream->buffer);
free(dirstream);
return 0;
}
windows_dir* windows_opendir(const char* dir)
{
if (!is_scan_winfind)
return windows_opendir_stream(dir);
else
return windows_opendir_find(dir);
}
struct windows_dirent* windows_readdir(windows_dir* dirstream)
{
if (!is_scan_winfind)
return windows_readdir_stream(dirstream);
else
return windows_readdir_find(dirstream);
}
int windows_closedir(windows_dir* dirstream)
{
if (!is_scan_winfind)
return windows_closedir_stream(dirstream);
else
return windows_closedir_find(dirstream);
}
int windows_dirent_hidden(struct dirent* dd)
{
return dd->d_stat.st_hidden;
}
const char* windows_stat_desc(struct stat* st)
{
return st->st_desc;
}
unsigned windows_sleep(unsigned seconds)
{
while (seconds) {
if (global_interrupt) /* SIGINT should stop the sleep */
break;
Sleep(1000);
--seconds;
}
return seconds;
}
void windows_usleep(uint64_t useconds)
{
Sleep(useconds / 1000);
}
int windows_link(const char* existing, const char* file)
{
wchar_t conv_buf_file[CONV_MAX];
wchar_t conv_buf_existing[CONV_MAX];
if (!CreateHardLinkW(convert(conv_buf_file, file), convert(conv_buf_existing, existing), 0)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
/**
* In Windows 10 allow creation of symlink by not privileged user.
*
* See: Symlinks in Windows 10!
* https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/#cQG7cx48oGH86lkI.97
* "Specify this flag to allow creation of symbolic links when the process is not elevated"
*/
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
#endif
int windows_symlink(const char* existing, const char* file)
{
wchar_t conv_buf_file[CONV_MAX];
wchar_t conv_buf_existing[CONV_MAX];
/*
* We must convert to the extended-length \\?\ format if the path is too long
* otherwise the link creation fails.
* But we don't want to always convert it, to avoid to recreate
* user symlinks different than they were before
*/
if (!CreateSymbolicLinkW(convert(conv_buf_file, file), convert_if_required(conv_buf_existing, existing),
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
) {
DWORD error = GetLastError();
if (GetLastError() != ERROR_INVALID_PARAMETER) {
windows_errno(error);
return -1;
}
/* retry without the new flag SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE */
if (!CreateSymbolicLinkW(convert(conv_buf_file, file), convert_if_required(conv_buf_existing, existing), 0)) {
windows_errno(GetLastError());
return -1;
}
}
return 0;
}
/* Adds missing definitions in MingW winnt.h */
#ifndef FSCTL_GET_REPARSE_POINT
#define FSCTL_GET_REPARSE_POINT 0x000900a8
#endif
#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
typedef struct _REPARSE_DATA_BUFFER {
DWORD ReparseTag;
WORD ReparseDataLength;
WORD Reserved;
_ANONYMOUS_UNION union {
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
BYTE DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#endif
int windows_readlink(const char* file, char* buffer, size_t size)
{
wchar_t conv_buf_file[CONV_MAX];
char conv_buf_name[CONV_MAX];
HANDLE h;
const char* name;
size_t len;
unsigned char rdb_buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
REPARSE_DATA_BUFFER* rdb = (REPARSE_DATA_BUFFER*)rdb_buffer;
BOOL ret;
DWORD n;
/*
* Open the handle of the file.
*
* Use FILE_FLAG_BACKUP_SEMANTICS to open directories (it's just ignored for files).
* Use FILE_FLAG_OPEN_REPARSE_POINT to open symbolic links and not the their target.
*/
h = CreateFileW(convert(conv_buf_file, file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
/* read the reparse point */
ret = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, 0, 0, rdb_buffer, sizeof(rdb_buffer), &n, 0);
if (!ret) {
windows_errno(GetLastError());
CloseHandle(h);
return -1;
}
CloseHandle(h);
/* check if it's really a symbolic link */
if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
errno = EINVAL;
return -1;
}
/* convert the name to UTF-8 */
name = u16tou8ex(conv_buf_name,
rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.PrintNameOffset,
rdb->SymbolicLinkReparseBuffer.PrintNameLength / 2, &len);
/* check for overflow */
if (len > size) {
len = size;
}
memcpy(buffer, name, len);
return len;
}
int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t uuid_size)
{
(void)device_path;
/* just use the volume serial number returned in the device parameter */
snprintf(uuid, uuid_size, "%08x", (unsigned)device_id);
log_tag("uuid:windows:%u:%s:\n", (unsigned)device_id, uuid);
return 0;
}
int filephy(const char* file, uint64_t size, uint64_t* physical)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
(void)size;
/* open the handle of the file */
h = CreateFileW(convert(conv_buf, file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, 0, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
if (!GetFilePhysicalOffset(h, physical)) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
return -1;
}
CloseHandle(h);
return 0;
}
int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space, char* fstype, size_t fstype_size, char* fslabel, size_t fslabel_size)
{
wchar_t conv_buf[CONV_MAX];
/* all FAT/exFAT/NTFS when managed from Windows have persistent inodes */
if (has_persistent_inode)
*has_persistent_inode = 1;
/* NTFS doesn't synchronize hardlinks metadata */
if (has_syncronized_hardlinks)
*has_syncronized_hardlinks = 0;
if (free_space || total_space) {
ULARGE_INTEGER total_bytes;
ULARGE_INTEGER total_free_bytes;
DWORD attr;
char dir[PATH_MAX];
if (strlen(path) + 1 > sizeof(dir)) {
windows_errno(ERROR_BUFFER_OVERFLOW);
return -1;
}
strcpy(dir, path);
/* get the file attributes */
attr = GetFileAttributesW(convert(conv_buf, dir));
if (attr == INVALID_FILE_ATTRIBUTES) {
DWORD error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND) {
windows_errno(error);
return -1;
}
/*
* If it doesn't exist, we assume a file
* and we check for the containing dir
*/
attr = 0;
}
/* if it's not a directory, truncate the file name */
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
char* slash = strrchr(dir, '/');
/**
* Cut the file name, but leave the last slash.
*
* This is done because a MSDN comment about using of UNC paths.
*
* MSDN 'GetDiskFreeSpaceEx function'
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa364937%28v=vs.85%29.aspx
* If this parameter is a UNC name, it must include a trailing backslash,
* for example, "\\MyServer\MyShare\".
*/
if (slash)
slash[1] = 0;
}
/*
* Get the free space of the directory
* note that it must be a directory
*/
if (!GetDiskFreeSpaceExW(convert(conv_buf, dir), 0, &total_bytes, &total_free_bytes)) {
windows_errno(GetLastError());
return -1;
}
if (total_space)
*total_space = total_bytes.QuadPart;
if (free_space)
*free_space = total_free_bytes.QuadPart;
}
if (fstype && fslabel) {
wchar_t volume_root[MAX_PATH];
wchar_t fs_name[MAX_PATH];
wchar_t vol_name[MAX_PATH];
fstype[0] = 0;
fslabel[0] = 0;
if (GetVolumePathNameW(convert(conv_buf, path), volume_root, MAX_PATH)) {
if (GetVolumeInformationW(volume_root, vol_name, MAX_PATH, 0, 0, 0, fs_name, MAX_PATH)) {
char u8[CONV_MAX];
size_t len;
char* ret;
ret = u16tou8ex_may_fail(u8, fs_name, wcslen(fs_name), &len);
if (ret != 0 && len + 1 <= fstype_size) {
memcpy(fstype, u8, len);
fstype[len] = 0;
}
ret = u16tou8ex_may_fail(u8, vol_name, wcslen(vol_name), &len);
if (ret != 0 && len + 1 <= fslabel_size) {
memcpy(fslabel, u8, len);
fslabel[len] = 0;
}
}
}
}
return 0;
}
int fssnapshot(const char* path, char* root, size_t root_size)
{
(void)path;
(void)root;
(void)root_size;
return -1;
}
int fssnapshot_create(const char* source, const char* parent_dir, const char* name)
{
(void)source;
(void)parent_dir;
(void)name;
return -1;
}
int fssnapshot_delete(const char* parent_dir, const char* name)
{
(void)parent_dir;
(void)name;
return -1;
}
int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* new_name)
{
(void)parent_dir;
(void)old_name;
(void)new_name;
return -1;
}
/* ensure to call the real C strerror() */
#undef strerror
const char* windows_strerror(int err)
{
/* get the normal C error from the specified err */
char* error;
char* previous;
const char* str = strerror(err);
size_t len = strlen(str);
/* adds space for GetLastError() */
len += 32;
/* allocate a new one */
error = malloc(len);
if (!error)
return str;
snprintf(error, len, "%s [%d/%u]", str, err, (unsigned)GetLastError());
/* get previous one, if any */
previous = windows_getspecific(last_error);
/* store in the thread local storage */
if (windows_setspecific(last_error, error) != 0) {
free(error);
return str;
}
free(previous);
return error;
}
ssize_t windows_read(int fd, void* buffer, size_t size)
{
HANDLE h;
DWORD count;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
if (!ReadFile(h, buffer, size, &count, 0)) {
windows_errno(GetLastError());
return -1;
}
return count;
}
ssize_t windows_write(int fd, const void* buffer, size_t size)
{
HANDLE h;
DWORD count;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
if (!WriteFile(h, buffer, size, &count, 0)) {
windows_errno(GetLastError());
return -1;
}
return count;
}
off_t windows_lseek(int fd, off_t offset, int whence)
{
HANDLE h;
LARGE_INTEGER pos;
LARGE_INTEGER ret;
if (fd == -1) {
errno = EBADF;
return -1;
}
/* we support only SEEK_SET */
if (whence != SEEK_SET) {
errno = EINVAL;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
pos.QuadPart = offset;
if (!SetFilePointerEx(h, pos, &ret, FILE_BEGIN)) {
windows_errno(GetLastError());
return -1;
}
return ret.QuadPart;
}
ssize_t windows_pread(int fd, void* buffer, size_t size, off_t offset)
{
HANDLE h;
OVERLAPPED overlapped;
DWORD count;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
retry:
memset(&overlapped, 0, sizeof(overlapped));
overlapped.Offset = offset & 0xFFFFFFFF;
overlapped.OffsetHigh = offset >> 32;
if (!ReadFile(h, buffer, size, &count, &overlapped)) {
DWORD err = GetLastError();
/*
* If Windows is not able to allocate memory from the PagedPool for the disk cache
* it could return the ERROR_NO_SYSTEM_RESOURCES error.
* In this case, the only possibility is to retry after a wait of few milliseconds.
*
* SQL Server reports operating system error 1450 or 1452 or 665 (retries)
* http://blogs.msdn.com/b/psssql/archive/2008/07/10/sql-server-reports-operating-system-error-1450-or-1452-or-665-retries.aspx
*
* 03-12-09 - ERROR_NO_SYSTEM_RESOURCES
* http://cbloomrants.blogspot.it/2009/03/03-12-09-errornosystemresources.html
*
* From SnapRAID Discussion Forum:
*
* Error reading file
* https://sourceforge.net/p/snapraid/discussion/1677233/thread/6657fdbf/
*
* Unexpected Windows ERROR_NO_SYSTEM_RESOURCES in pwrite(), retrying...
* https://sourceforge.net/p/snapraid/discussion/1677233/thread/a7c25ba9/
*/
if (err == ERROR_NO_SYSTEM_RESOURCES) {
log_fatal(EEXTERNAL, "Unexpected Windows ERROR_NO_SYSTEM_RESOURCES in pread(), retrying...\n");
Sleep(50);
goto retry;
}
windows_errno(err);
return -1;
}
return count;
}
ssize_t windows_pwrite(int fd, const void* buffer, size_t size, off_t offset)
{
HANDLE h;
OVERLAPPED overlapped;
DWORD count;
if (fd == -1) {
errno = EBADF;
return -1;
}
h = (HANDLE)_get_osfhandle(fd);
if (h == INVALID_HANDLE_VALUE) {
errno = EBADF;
return -1;
}
retry:
memset(&overlapped, 0, sizeof(overlapped));
overlapped.Offset = offset & 0xFFFFFFFF;
overlapped.OffsetHigh = offset >> 32;
if (!WriteFile(h, buffer, size, &count, &overlapped)) {
DWORD err = GetLastError();
/* See windows_pread() for comments on this error management */
if (err == ERROR_NO_SYSTEM_RESOURCES) {
log_fatal(EEXTERNAL, "Unexpected Windows ERROR_NO_SYSTEM_RESOURCES in pwrite(), retrying...\n");
Sleep(50);
goto retry;
}
windows_errno(err);
return -1;
}
return count;
}
size_t windows_direct_size(void)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
/*
* MSDN 'File Buffering'
* https://msdn.microsoft.com/en-us/library/windows/desktop/cc644950%28v=vs.85%29.aspx
*
* "Therefore, in most situations, page-aligned memory will also be sector-aligned,"
* "because the case where the sector size is larger than the page size is rare."
*/
return si.dwPageSize;
}
uint64_t os_tick(void)
{
LARGE_INTEGER t;
uint64_t r;
/*
* Ensure to return a strict monotone os_tick counter.
*
* We had reports of invalid stats due faulty High Precision Event Timer.
* See: https://sourceforge.net/p/snapraid/discussion/1677233/thread/a2122fd6/
*/
windows_mutex_lock(&tick_lock);
/*
* MSDN 'QueryPerformanceCounter'
* "On systems that run Windows XP or later, the function"
* "will always succeed and will thus never return zero."
*/
r = 0;
if (QueryPerformanceCounter(&t))
r = t.QuadPart;
if (r < tick_last)
r = tick_last;
tick_last = r;
windows_mutex_unlock(&tick_lock);
return r;
}
uint64_t os_tick_ms(void)
{
return GetTickCount64();
}
int randomize(void* void_ptr, size_t size)
{
size_t i;
unsigned char* ptr = void_ptr;
/* try RtlGenRandom */
if (ptr_RtlGenRandom != 0 && ptr_RtlGenRandom(ptr, size) != 0)
return 0;
/* fallback to standard rand */
for (i = 0; i < size; ++i)
ptr[i] = random_u8();
return 0;
}
/**
* Get the device file from a path inside the device.
*/
static int devresolve(const char* mount, char* file, size_t file_size, char* wfile, size_t wfile_size)
{
wchar_t conv_buf_mount[CONV_MAX];
char conv_buf_volume_guid[CONV_MAX];
WCHAR volume_mount[MAX_PATH];
WCHAR volume_guid[MAX_PATH];
DWORD i;
char* p;
/* get the volume mount point from the disk path */
if (!GetVolumePathNameW(convert(conv_buf_mount, mount), volume_mount, sizeof(volume_mount) / sizeof(WCHAR))) {
windows_errno(GetLastError());
return -1;
}
/* get the volume GUID path from the mount point */
if (!GetVolumeNameForVolumeMountPointW(volume_mount, volume_guid, sizeof(volume_guid) / sizeof(WCHAR))) {
windows_errno(GetLastError());
return -1;
}
/*
* Remove the final slash, otherwise CreateFile() opens the file-system
* and not the volume
*/
i = 0;
while (volume_guid[i] != 0)
++i;
if (i != 0 && volume_guid[i - 1] == '\\')
volume_guid[i - 1] = 0;
pathcpy(wfile, wfile_size, u16tou8(conv_buf_volume_guid, volume_guid));
/* get the GUID start { */
p = strchr(wfile, '{');
if (!p)
p = wfile;
else
++p;
pathprint(file, file_size, "/dev/vol%s", p);
/* cut GUID end } */
p = strrchr(file, '}');
if (p)
*p = 0;
return 0;
}
/**
* Read a device tree filling the specified list of disk_t entries.
*/
static int devtree(devinfo_t* parent, tommy_list* list)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
unsigned char vde_buffer[sizeof(VOLUME_DISK_EXTENTS)];
VOLUME_DISK_EXTENTS* vde = (VOLUME_DISK_EXTENTS*)&vde_buffer;
unsigned vde_size = sizeof(vde_buffer);
void* vde_alloc = 0;
BOOL ret;
DWORD n;
DWORD i;
/* open the volume */
h = CreateFileW(convert(conv_buf, parent->wfile), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
}
/* get the physical extents of the volume */
ret = DeviceIoControl(h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, 0, 0, vde, vde_size, &n, 0);
if (!ret) {
DWORD error = GetLastError();
if (error != ERROR_MORE_DATA) {
CloseHandle(h);
windows_errno(error);
return -1;
}
/* more than one extends, allocate more space */
vde_size = sizeof(VOLUME_DISK_EXTENTS) + vde->NumberOfDiskExtents * sizeof(DISK_EXTENT);
vde_alloc = malloc_nofail(vde_size);
vde = vde_alloc;
/* retry with more space */
ret = DeviceIoControl(h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, 0, 0, vde, vde_size, &n, 0);
}
if (!ret) {
DWORD error = GetLastError();
CloseHandle(h);
free(vde_alloc);
windows_errno(error);
return -1;
}
for (i = 0; i < vde->NumberOfDiskExtents; ++i) {
devinfo_t* devinfo;
devinfo = calloc_nofail(1, sizeof(devinfo_t));
pathcpy(devinfo->name, sizeof(devinfo->name), parent->name);
pathcpy(devinfo->smartctl, sizeof(devinfo->smartctl), parent->smartctl);
memcpy(devinfo->smartignore, parent->smartignore, sizeof(devinfo->smartignore));
devinfo->device = vde->Extents[i].DiskNumber;
pathprint(devinfo->file, sizeof(devinfo->file), "/dev/pd%" PRIu64, devinfo->device);
pathprint(devinfo->wfile, sizeof(devinfo->wfile), "\\\\.\\PhysicalDrive%" PRIu64, devinfo->device);
devinfo->parent = parent;
/* insert in the list */
tommy_list_insert_tail(list, &devinfo->node, devinfo);
}
if (!CloseHandle(h)) {
windows_errno(GetLastError());
free(vde_alloc);
return -1;
}
free(vde_alloc);
return 0;
}
/**
* Compute disk usage by aggregating access statistics.
*
* On many Windows versions, these raw disk performance counters are disabled by default to save overhead.
*
* If DeviceIoControl call with IOCTL_DISK_PERFORMANCE returns zeros or fails, the counters are likely disabled.
*
* Run the following command from an elevated command prompt:
*
* diskperf -y
*
* This change usually requires a reboot to take effect at the driver level.
*/
static int devstat(uint64_t device, const char* name, const char* wfile, uint64_t* count)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
BOOL ret;
DISK_PERFORMANCE ds;
DWORD bytes;
char file[128];
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* open the volume */
h = CreateFileW(convert(conv_buf, wfile), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (h == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
bytes = 0;
ret = DeviceIoControl(h, IOCTL_DISK_PERFORMANCE, NULL, 0, &ds, sizeof(ds), &bytes, NULL);
if (!ret) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
if (!CloseHandle(h)) {
DWORD error = GetLastError();
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
*count = ds.ReadCount + ds.WriteCount;
return 0;
}
/**
* Get SMART attributes.
*/
static int devsmart(uint64_t device, const char* name, const char* smartctl, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* inter)
{
char conv_buf[CONV_MAX];
WCHAR cmd[MAX_PATH + 128];
char file[128];
FILE* f;
int ret;
int count;
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[128];
snprintf(option, sizeof(option), smartctl, file);
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a %s", exedir, option);
} else {
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a %s", exedir, file);
}
count = 0;
retry:
log_tag("smartctl:%s:%s:run: %s\n", file, name, u16tou8(conv_buf, cmd));
f = _wpopen(cmd, L"rt");
if (!f) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:shell\n", file, name);
log_fatal(errno, "Failed to run '%s' (from popen).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
if (smartctl_attribute(f, file, name, smart, info, serial, family, model, inter) != 0) {
/* LCOV_EXCL_START */
pclose(f);
log_tag("device:%s:%s:shell\n", file, name);
return -1;
/* LCOV_EXCL_STOP */
}
ret = pclose(f);
log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
if (ret == -1) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:shell\n", file, name);
log_fatal(errno, "Failed to run '%s' (from pclose).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
/* if first try without custom smartctl command */
if (count == 0 && smartctl[0] == 0) {
/*
* Handle some common cases in Windows.
*
* Sometimes the "type" autodetection is wrong, and the command fails at identification
* stage, returning with error 2, or even with error 0, and with no info at all.
* We detect this condition checking the PowerOnHours, Size and RotationRate attributes.
*
* In such conditions we retry using the "sat" type, that often allows to proceed.
*
* Note that getting error 4 is instead very common, even with full info gathering.
*/
if ((ret == 0 || ret == 2)
&& smart[SMART_POWER_ON_HOURS].raw == SMART_UNASSIGNED
&& info[INFO_SIZE] == SMART_UNASSIGNED
&& info[INFO_ROTATION_RATE] == SMART_UNASSIGNED
) {
/* retry using the "sat" type */
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a -d sat %s", exedir, file);
++count;
goto retry;
}
}
/* store the smartctl return value */
smart[SMART_FLAGS].raw = ret;
return 0;
}
static int serial_descriptor(PSTORAGE_DEVICE_DESCRIPTOR descriptor, size_t size, char* output, size_t output_size)
{
if (descriptor->SerialNumberOffset == 0
|| descriptor->SerialNumberOffset == (DWORD)-1
|| descriptor->SerialNumberOffset >= size)
return -1;
const char* raw = (const char*)descriptor + descriptor->SerialNumberOffset;
size_t len = 0;
/* find the length, not assuming a 0 ending */
while (descriptor->SerialNumberOffset + len < size && raw[len] != 0)
++len;
if (len < 6) /* a serial cannot be so short */
return -1;
if (len + 1 > output_size)
return -1;
/* all must be printable */
for (size_t i = 0; i < len; ++i) {
if (!isprint((unsigned char)raw[i]))
return -1;
}
memcpy(output, raw, len);
output[len] = 0;
strtrim(output);
return 0;
}
static void devattr_property(HANDLE h, char* serial, char* model, char* inter)
{
STORAGE_PROPERTY_QUERY query = { 0 };
STORAGE_DESCRIPTOR_HEADER header = { 0 };
DWORD bytes;
query.PropertyId = StorageDeviceProperty;
query.QueryType = PropertyStandardQuery;
if (!DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &header, sizeof(header), &bytes, 0))
return;
if (header.Size == 0)
return;
/* allocate buffer and query full descriptor */
BYTE* buffer = malloc(header.Size);
if (!buffer)
return;
if (!DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), buffer, header.Size, &bytes, 0)) {
free(buffer);
return;
}
STORAGE_DEVICE_DESCRIPTOR* desc = (STORAGE_DEVICE_DESCRIPTOR*)buffer;
/* extract strings (offsets are from start of descriptor) */
if (model && *model == 0 && desc->ProductIdOffset) {
snprintf(model, SMART_MAX, "%s", (char*)(buffer + desc->ProductIdOffset));
strtrim(model);
}
if (serial && *serial == 0) {
/*
* This is the RAW serial, maybe HEX encoded, maybe BYTES SWAPPED,
* but we don't care because we just need any identifier.
*
* If smartctl was not able to read it, we now accept anything.
*/
serial_descriptor(desc, bytes, serial, SMART_MAX);
}
#define BusTypeVirtual 0x0E
#define BusTypeFileBackedVirtual 0x0F
#define BusTypeSpaces 0x10
#define BusTypeNvme 0x11
#define BusTypeSCM 0x12
#define BusTypeUfs 0x13
#define BusTypeNvmeof 0x14
/* always override interface as more detailed than smartctl */
const char* type = 0;
switch ((int)desc->BusType) { /* cast to int to avoid warnings about enum unlisted */
case BusTypeScsi : type = "SCSI"; break;
case BusTypeAtapi : type = "ATAPI"; break;
case BusTypeAta : type = "ATA"; break;
case BusType1394 : type = "FireWire"; break;
case BusTypeSsa : type = "SSA"; break;
case BusTypeFibre : type = "Fibre"; break;
case BusTypeUsb : type = "USB"; break;
case BusTypeRAID : type = "RAID"; break;
case BusTypeiScsi : type = "iSCSI"; break;
case BusTypeSas : type = "SAS"; break;
case BusTypeSata : type = "SATA"; break;
case BusTypeSd : type = "SD"; break;
case BusTypeMmc : type = "MMC"; break;
case BusTypeVirtual : type = "Virtual"; break;
case BusTypeFileBackedVirtual : type = "Virtual"; break;
case BusTypeSpaces : type = "Storage Spaces"; break;
case BusTypeNvme : type = "NVMe"; break;
case BusTypeSCM : type = "SCM"; break;
case BusTypeUfs : type = "UFS"; break;
case BusTypeNvmeof : type = "NVMe"; break;
default :
}
if (inter && type)
snprintf(inter, SMART_MAX, "%s", type);
free(buffer);
}
static void devattr_size(HANDLE h, uint64_t* size)
{
DISK_GEOMETRY_EX geom = { 0 };
DWORD bytes;
if (!DeviceIoControl(h, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, 0, 0, &geom, sizeof(geom), &bytes, 0))
return;
*size = geom.DiskSize.QuadPart;
}
static void devattr_rotational(HANDLE h, uint64_t* rotational)
{
STORAGE_PROPERTY_QUERY query = { 0 };
DEVICE_SEEK_PENALTY_DESCRIPTOR desc = { 0 };
DWORD bytes;
query.PropertyId = StorageDeviceSeekPenaltyProperty;
query.QueryType = PropertyStandardQuery;
if (!DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &desc, sizeof(desc), &bytes, 0))
return;
if (bytes < sizeof(desc))
return;
if (desc.Version != sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR) || desc.Size != sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR))
return;
*rotational = desc.IncursSeekPenalty ? 1 : 0;
}
/**
* Get device attributes.
*/
static void devattr(uint64_t device, const char* name, const char* wfile, uint64_t* info, char* serial, char* family, char* model, char* interf)
{
HANDLE h;
wchar_t conv_buf[CONV_MAX];
char file[128];
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
(void)family; /* not available, smartctl uses an internal database to get it */
/* open the volume */
h = CreateFileW(convert(conv_buf, wfile), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, 0);
if (h == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return;
}
if (info[INFO_SIZE] == SMART_UNASSIGNED)
devattr_size(h, &info[INFO_SIZE]);
if (info[INFO_ROTATION_RATE] == SMART_UNASSIGNED)
devattr_rotational(h, &info[INFO_ROTATION_RATE]);
if (*model == 0 || *serial == 0)
devattr_property(h, serial, model, interf);
if (!CloseHandle(h)) {
DWORD error = GetLastError();
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return;
}
}
/**
* Get POWER state
*/
static int devprobe(uint64_t device, const char* name, const char* smartctl, int* power, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* interf)
{
char conv_buf[CONV_MAX];
WCHAR cmd[MAX_PATH + 128];
char file[128];
FILE* f;
int ret;
int count;
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[128];
snprintf(option, sizeof(option), smartctl, file);
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -n standby,3 -a %s", exedir, option);
} else {
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -n standby,3 -a %s", exedir, file);
}
count = 0;
retry:
log_tag("smartctl:%s:%s:run: %s\n", file, name, u16tou8(conv_buf, cmd));
f = _wpopen(cmd, L"rt");
if (!f) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:shell\n", file, name);
log_fatal(errno, "Failed to run '%s' (from popen).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
if (smartctl_attribute(f, file, name, smart, info, serial, family, model, interf) != 0) {
/* LCOV_EXCL_START */
pclose(f);
log_tag("device:%s:%s:shell\n", file, name);
return -1;
/* LCOV_EXCL_STOP */
}
ret = pclose(f);
log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
if (ret == -1) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:shell\n", file, name);
log_fatal(errno, "Failed to run '%s' (from pclose).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
/* if first try without custom smartctl command */
if (count == 0 && smartctl[0] == 0) {
/*
* Handle some common cases in Windows.
*
* Sometimes the "type" autodetection is wrong, and the command fails at identification
* stage, returning with error 2.
*
* In such conditions we retry using the "sat" type, that often allows to proceed.
*/
if (ret == 2) {
/* retry using the "sat" type */
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -a -d sat %s", exedir, file);
++count;
goto retry;
}
}
if (ret == 3) {
log_tag("attr:%s:%s:power:standby\n", file, name);
*power = POWER_STANDBY;
} else {
log_tag("attr:%s:%s:power:active\n", file, name);
*power = POWER_ACTIVE;
/* store the smartctl return value */
if (smart)
smart[SMART_FLAGS].raw = ret;
}
return 0;
}
/**
* Spin down a specific device.
*/
static int devdown(uint64_t device, const char* name, const char* smartctl)
{
char conv_buf[CONV_MAX];
WCHAR cmd[MAX_PATH + 128];
char file[128];
FILE* f;
int ret;
int count;
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* if there is a custom smartctl command */
if (smartctl[0]) {
char option[128];
snprintf(option, sizeof(option), smartctl, file);
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -s standby,now %s", exedir, option);
} else {
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -s standby,now %s", exedir, file);
}
count = 0;
retry:
log_tag("smartctl:%s:%s:run: %s\n", file, name, u16tou8(conv_buf, cmd));
f = _wpopen(cmd, L"rt");
if (!f) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:shell\n", file, name);
log_fatal(errno, "Failed to run '%s' (from popen).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
if (smartctl_flush(f, file, name) != 0) {
/* LCOV_EXCL_START */
pclose(f);
log_tag("device:%s:%s:shell\n", file, name);
return -1;
/* LCOV_EXCL_STOP */
}
ret = pclose(f);
log_tag("smartctl:%s:%s:ret: %x\n", file, name, ret);
if (ret == -1) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:shell\n", file, name);
log_fatal(errno, "Failed to run '%s' (from pclose).\n", u16tou8(conv_buf, cmd));
return -1;
/* LCOV_EXCL_STOP */
}
/* if first try without custom smartctl command */
if (count == 0 && smartctl[0] == 0) {
/*
* Handle some common cases in Windows.
*
* Sometimes the "type" autodetection is wrong, and the command fails at identification
* stage, returning with error 2.
*
* In such conditions we retry using the "sat" type, that often allows to proceed.
*/
if (ret == 2) {
/* retry using the "sat" type */
snwprintf(cmd, sizeof(cmd), L"\"%lssmartctl.exe\" -s standby,now -d sat %s", exedir, file);
++count;
goto retry;
}
}
if (ret != 0) {
/* LCOV_EXCL_START */
log_tag("device:%s:%s:exit:%d\n", file, name, ret);
log_fatal(errno, "Failed to run '%s' with return code %xh.\n", u16tou8(conv_buf, cmd), ret);
return -1;
/* LCOV_EXCL_STOP */
}
log_tag("attr:%s:%s:power:down\n", file, name);
return 0;
}
/**
* Spin down a specific device if it's up
*/
static int devdownifup(uint64_t device, const char* name, const char* smartctl, int* power)
{
*power = POWER_UNKNOWN;
if (devprobe(device, name, smartctl, power, 0, 0, 0, 0, 0, 0) != 0)
return -1;
if (*power == POWER_ACTIVE)
return devdown(device, name, smartctl);
return 0;
}
/**
* Spin up a device.
*/
static int devup(uint64_t device, const char* name, const char* wfile)
{
wchar_t conv_buf[CONV_MAX];
HANDLE h;
BOOL ret;
DISK_GEOMETRY dg;
DWORD bytes;
void* buffer;
char file[128];
snprintf(file, sizeof(file), "/dev/pd%" PRIu64, device);
/* open the volume */
h = CreateFileW(convert(conv_buf, wfile), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, 0);
if (h == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
bytes = 0;
ret = DeviceIoControl(h, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &dg, sizeof(dg), &bytes, NULL);
if (!ret) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
buffer = VirtualAlloc(NULL, dg.BytesPerSector, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!buffer) {
DWORD error = GetLastError();
CloseHandle(h);
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
bytes = 0;
if (!ReadFile(h, buffer, dg.BytesPerSector, &bytes, NULL)) {
DWORD error = GetLastError();
CloseHandle(h);
VirtualFree(buffer, 0, MEM_RELEASE);
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
if (!CloseHandle(h)) {
DWORD error = GetLastError();
VirtualFree(buffer, 0, MEM_RELEASE);
windows_errno(error);
log_tag("device:%s:%s:error:%lu\n", file, name, error);
return -1;
}
VirtualFree(buffer, 0, MEM_RELEASE);
log_tag("attr:%s:%s:power:up\n", file, name);
return 0;
}
/**
* Thread for spinning up.
*/
static void* thread_spinup(void* arg)
{
devinfo_t* devinfo = arg;
uint64_t start;
start = os_tick_ms();
if (devup(devinfo->device, devinfo->name, devinfo->wfile) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
msg_status("Spunup device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
/* after the spin up, get SMART info */
if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
/*
* Retrieve some attributes directly from the system.
*
* smartctl intentionally skips queries on devices in standby mode
* to prevent accidentally spinning them up.
*/
devattr(devinfo->device, devinfo->name, devinfo->wfile, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
return 0;
}
/**
* Thread for spinning down.
*/
static void* thread_spindown(void* arg)
{
devinfo_t* devinfo = arg;
uint64_t start;
start = os_tick_ms();
if (devdown(devinfo->device, devinfo->name, devinfo->smartctl) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
return 0;
}
/**
* Thread for spinning down.
*/
static void* thread_spindownifup(void* arg)
{
devinfo_t* devinfo = arg;
uint64_t start;
int power;
start = os_tick_ms();
if (devdownifup(devinfo->device, devinfo->name, devinfo->smartctl, &power) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
if (power == POWER_ACTIVE)
msg_status("Spundown device '%s' for disk '%s' in %" PRIu64 " ms.\n", devinfo->file, devinfo->name, os_tick_ms() - start);
return 0;
}
/**
* Thread for getting smart info.
*/
static void* thread_smart(void* arg)
{
devinfo_t* devinfo = arg;
if (devsmart(devinfo->device, devinfo->name, devinfo->smartctl, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
/*
* Retrieve some attributes directly from the system.
*
* smartctl intentionally skips queries on devices in standby mode
* to prevent accidentally spinning them up.
*/
devattr(devinfo->device, devinfo->name, devinfo->wfile, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
return 0;
}
/**
* Thread for getting power info.
*/
static void* thread_probe(void* arg)
{
devinfo_t* devinfo = arg;
if (devprobe(devinfo->device, devinfo->name, devinfo->smartctl, &devinfo->power, devinfo->smart, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf) != 0) {
/* LCOV_EXCL_START */
return (void*)-1;
/* LCOV_EXCL_STOP */
}
/*
* Retrieve some attributes directly from the system.
*
* smartctl intentionally skips queries on devices in standby mode
* to prevent accidentally spinning them up.
*/
devattr(devinfo->device, devinfo->name, devinfo->wfile, devinfo->info, devinfo->serial, devinfo->family, devinfo->model, devinfo->interf);
return 0;
}
static int device_thread(tommy_list* list, void* (*func)(void* arg))
{
int fail = 0;
tommy_node* i;
/* starts all threads */
for (i = tommy_list_head(list); i != 0; i = i->next) {
devinfo_t* devinfo = i->data;
thread_create(&devinfo->thread, func, devinfo);
}
/* joins all threads */
for (i = tommy_list_head(list); i != 0; i = i->next) {
devinfo_t* devinfo = i->data;
void* retval;
thread_join(devinfo->thread, &retval);
if (retval != 0)
++fail;
}
if (fail != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
int devquery(tommy_list* high, tommy_list* low, int operation)
{
tommy_node* i;
void* (*func)(void* arg) = 0;
/* for each device */
for (i = tommy_list_head(high); i != 0; i = i->next) {
devinfo_t* devinfo = i->data;
uint64_t access_stat;
if (devresolve(devinfo->mount, devinfo->file, sizeof(devinfo->file), devinfo->wfile, sizeof(devinfo->wfile)) != 0) {
/* LCOV_EXCL_START */
log_fatal(EEXTERNAL, "Failed to resolve path '%s'.\n", devinfo->mount);
return -1;
/* LCOV_EXCL_STOP */
}
/* retrieve access stat for the high level device */
if (devstat(devinfo->device, devinfo->name, devinfo->wfile, &access_stat) == 0) {
/* cumulate access stat in the first split */
if (devinfo->split)
devinfo->split->access_stat += access_stat;
else
devinfo->access_stat += access_stat;
}
/* expand the tree of devices */
if (devtree(devinfo, low) != 0) {
/* LCOV_EXCL_START */
log_fatal(EEXTERNAL, "Failed to expand device '%s'.\n", devinfo->file);
return -1;
/* LCOV_EXCL_STOP */
}
}
switch (operation) {
case DEVICE_UP : func = thread_spinup; break;
case DEVICE_DOWN : func = thread_spindown; break;
case DEVICE_SMART : func = thread_smart; break;
case DEVICE_PROBE : func = thread_probe; break;
case DEVICE_DOWNIFUP : func = thread_spindownifup; break;
}
if (!func)
return 0;
return device_thread(low, func);
}
int devmap(void)
{
for (int i = 0; i < 32; i++) {
char wfile[PATH_MAX];
wchar_t conv_buf[CONV_MAX];
pathprint(wfile, sizeof(wfile), "\\\\.\\PhysicalDrive%d", i);
HANDLE h;
/* open the volume */
h = CreateFileW(convert(conv_buf, wfile), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, 0);
if (h == INVALID_HANDLE_VALUE)
continue;
char serial[SMART_MAX];
serial[0] = 0;
devattr_property(h, serial, 0, 0);
if (serial[0]) {
log_tag("map:/dev/pd%d:%s\n", i, esc_tag(serial));
}
CloseHandle(h);
}
return 0;
}
/****************************************************************************/
/* pthread like interface */
int windows_mutex_init(windows_mutex_t* mutex, void* attr)
{
(void)attr;
InitializeCriticalSection(mutex);
return 0;
}
int windows_mutex_destroy(windows_mutex_t* mutex)
{
DeleteCriticalSection(mutex);
return 0;
}
int windows_mutex_lock(windows_mutex_t* mutex)
{
EnterCriticalSection(mutex);
return 0;
}
int windows_mutex_unlock(windows_mutex_t* mutex)
{
LeaveCriticalSection(mutex);
return 0;
}
int windows_cond_init(windows_cond_t* cond, void* attr)
{
(void)attr;
InitializeConditionVariable(cond);
return 0;
}
int windows_cond_destroy(windows_cond_t* cond)
{
/* note that in Windows there is no DeleteConditionVariable() to call */
(void)cond;
return 0;
}
int windows_cond_signal(windows_cond_t* cond)
{
WakeConditionVariable(cond);
return 0;
}
int windows_cond_broadcast(windows_cond_t* cond)
{
WakeAllConditionVariable(cond);
return 0;
}
int windows_cond_wait(windows_cond_t* cond, windows_mutex_t* mutex)
{
if (!SleepConditionVariableCS(cond, mutex, INFINITE))
return -1;
return 0;
}
struct windows_key_context {
void (*func)(void*);
DWORD key;
tommy_node node;
};
/* list of all keys with destructor */
static tommy_list windows_key_list = { 0 };
int windows_key_create(windows_key_t* key, void (*destructor)(void*))
{
struct windows_key_context* context;
context = malloc(sizeof(struct windows_key_context));
if (!context)
return -1;
context->func = destructor;
context->key = TlsAlloc();
if (context->key == 0xFFFFFFFF) {
windows_errno(GetLastError());
free(context);
return -1;
}
/* insert in the list of destructors */
if (context->func)
tommy_list_insert_tail(&windows_key_list, &context->node, context);
*key = context;
return 0;
}
int windows_key_delete(windows_key_t void_key)
{
struct windows_key_context* key = void_key;
/* use the destructor for the local variable and remove from the list of destructors */
if (key->func) {
void* value = windows_getspecific(key);
if (value)
key->func(value);
tommy_list_remove_existing(&windows_key_list, &key->node);
}
TlsFree(key->key);
free(key);
return 0;
}
void* windows_getspecific(windows_key_t key)
{
struct windows_key_context* context = key;
return TlsGetValue(context->key);
}
int windows_setspecific(windows_key_t key, void* value)
{
struct windows_key_context* context = key;
if (!TlsSetValue(context->key, value)) {
windows_errno(GetLastError());
return -1;
}
return 0;
}
struct windows_thread_context {
HANDLE h;
unsigned id;
void* (*func)(void*);
void* arg;
void* ret;
};
/* forwarder to change the function declaration */
static unsigned __stdcall windows_thread_func(void* arg)
{
struct windows_thread_context* context = arg;
tommy_node* i;
context->ret = context->func(context->arg);
/* call the destructor of all the keys */
i = tommy_list_head(&windows_key_list);
while (i) {
struct windows_key_context* key = i->data;
if (key->func) {
void* value = windows_getspecific(key);
if (value)
key->func(value);
}
i = i->next;
}
return 0;
}
int windows_create(thread_id_t* thread, void* attr, void* (*func)(void*), void* arg)
{
struct windows_thread_context* context;
(void)attr;
context = malloc(sizeof(struct windows_thread_context));
if (!context)
return -1;
context->func = func;
context->arg = arg;
context->ret = 0;
context->h = (void*)_beginthreadex(0, 0, windows_thread_func, context, 0, &context->id);
if (context->h == 0) {
free(context);
return -1;
}
*thread = context;
return 0;
}
int windows_join(thread_id_t thread, void** retval)
{
struct windows_thread_context* context = thread;
if (WaitForSingleObject(context->h, INFINITE) != WAIT_OBJECT_0) {
windows_errno(GetLastError());
CloseHandle(context->h);
free(context);
return -1;
}
if (!CloseHandle(context->h)) {
windows_errno(GetLastError());
free(context);
return -1;
}
*retval = context->ret;
free(context);
return 0;
}
int ambient_temperature(void)
{
return 0;
}
#endif