Unverified Commit ac10f36b authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #596 from capnproto/filesystem-win32

Implement KJ Filesystem API on Win32
parents 5822b19c 38541d69
......@@ -142,10 +142,12 @@ includekj_HEADERS = \
src/kj/mutex.h \
src/kj/thread.h \
src/kj/threadlocal.h \
src/kj/filesystem.h \
src/kj/async-prelude.h \
src/kj/async.h \
src/kj/async-inl.h \
src/kj/time.h \
src/kj/timer.h \
src/kj/async-unix.h \
src/kj/async-win32.h \
src/kj/async-io.h \
......@@ -230,6 +232,10 @@ libkj_la_SOURCES= \
src/kj/io.c++ \
src/kj/mutex.c++ \
src/kj/thread.c++ \
src/kj/time.c++ \
src/kj/filesystem.c++ \
src/kj/filesystem-disk-unix.c++ \
src/kj/filesystem-disk-win32.c++ \
src/kj/test-helpers.c++ \
src/kj/main.c++ \
src/kj/parse/char.c++
......@@ -248,7 +254,7 @@ libkj_async_la_SOURCES= \
src/kj/async-io.c++ \
src/kj/async-io-unix.c++ \
src/kj/async-io-win32.c++ \
src/kj/time.c++
src/kj/timer.c++
libkj_http_la_LIBADD = libkj-async.la libkj.la $(ASYNC_LIBS) $(PTHREAD_LIBS)
libkj_http_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
......@@ -449,7 +455,9 @@ capnp_test_LDADD = \
libkj-http.la \
libkj-async.la \
libkj-test.la \
libkj.la
libkj.la \
$(ASYNC_LIBS) \
$(PTHREAD_LIBS)
endif !LITE_MODE
......@@ -473,6 +481,8 @@ capnp_test_SOURCES = \
src/kj/mutex-test.c++ \
src/kj/threadlocal-test.c++ \
src/kj/threadlocal-pthread-test.c++ \
src/kj/filesystem-test.c++ \
src/kj/filesystem-disk-test.c++ \
src/kj/test-test.c++ \
src/capnp/common-test.c++ \
src/capnp/blob-test.c++ \
......
......@@ -20,6 +20,10 @@ set(kj_sources_heavy
refcount.c++
string-tree.c++
encoding.c++
time.c++
filesystem.c++
filesystem-disk-unix.c++
filesystem-disk-win32.c++
parse/char.c++
)
if(NOT CAPNP_LITE)
......@@ -48,6 +52,8 @@ set(kj_headers
mutex.h
thread.h
threadlocal.h
filesystem.h
time.h
main.h
windows-sanity.h
)
......@@ -105,7 +111,7 @@ set(kj-async_sources
async-io-win32.c++
async-io.c++
async-io-unix.c++
time.c++
timer.c++
)
set(kj-async_headers
async-prelude.h
......@@ -114,7 +120,7 @@ set(kj-async_headers
async-unix.h
async-win32.h
async-io.h
time.h
timer.h
)
if(NOT CAPNP_LITE)
add_library(kj-async ${kj-async_sources})
......@@ -188,6 +194,8 @@ if(BUILD_TESTING)
one-of-test.c++
function-test.c++
threadlocal-pthread-test.c++
filesystem-test.c++
filesystem-disk-test.c++
parse/common-test.c++
parse/char-test.c++
compat/url-test.c++
......
......@@ -29,7 +29,7 @@
#include "async.h"
#include "function.h"
#include "thread.h"
#include "time.h"
#include "timer.h"
struct sockaddr;
......
......@@ -31,7 +31,7 @@
#endif
#include "async.h"
#include "time.h"
#include "timer.h"
#include "vector.h"
#include "io.h"
#include <signal.h>
......
......@@ -27,7 +27,7 @@
#endif
#include "async.h"
#include "time.h"
#include "timer.h"
#include "io.h"
#include <atomic>
#include <inttypes.h>
......
......@@ -169,12 +169,6 @@ KJ_TEST("readiness IO: read many even") {
auto io = setupAsyncIo();
auto pipe = io.provider->newOneWayPipe();
// Abort on hang.
// TODO(now): Remove this.
io.provider->getTimer().afterDelay(1 * kj::SECONDS).then([]() {
abort();
}).detach([](kj::Exception&&) {});
char dummy[8192];
for (auto i: kj::indices(dummy)) {
dummy[i] = "ba"[i%2];
......
......@@ -34,6 +34,7 @@
#define NOIME 1
#include <windows.h>
#include "windows-sanity.h"
#include "encoding.h"
#endif
namespace kj {
......@@ -354,29 +355,33 @@ void Debug::Fault::init(
#if _WIN32
void Debug::Fault::init(
const char* file, int line, Win32Error osErrorNumber,
const char* file, int line, Win32Result osErrorNumber,
const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
LPVOID ptr;
// TODO(soon): Use FormatMessageW() instead.
// TODO(soon): Why doesn't this work for winsock errors?
DWORD result = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
DWORD result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, osErrorNumber.number,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &ptr, 0, NULL);
(LPWSTR) &ptr, 0, NULL);
String message;
if (result > 0) {
KJ_DEFER(LocalFree(ptr));
exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
makeDescriptionImpl(SYSCALL, condition, 0, reinterpret_cast<char*>(ptr),
macroArgs, argValues));
const wchar_t* desc = reinterpret_cast<wchar_t*>(ptr);
size_t len = wcslen(desc);
if (len > 0 && desc[len-1] == '\n') --len;
if (len > 0 && desc[len-1] == '\r') --len;
message = kj::str('#', osErrorNumber.number, ' ',
decodeWideString(arrayPtr(desc, len)));
} else {
auto message = kj::str("win32 error code: ", osErrorNumber.number);
exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
makeDescriptionImpl(SYSCALL, condition, 0, message.cStr(),
macroArgs, argValues));
message = kj::str("win32 error code: ", osErrorNumber.number);
}
exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
makeDescriptionImpl(SYSCALL, condition, 0, message.cStr(),
macroArgs, argValues));
}
#endif
......@@ -395,8 +400,8 @@ int Debug::getOsErrorNumber(bool nonblocking) {
}
#if _WIN32
Debug::Win32Error Debug::getWin32Error() {
return Win32Error(::GetLastError());
uint Debug::getWin32ErrorCode() {
return ::GetLastError();
}
#endif
......
......@@ -168,18 +168,18 @@ namespace kj {
#if _WIN32
#define KJ_WIN32(call, ...) \
if (::kj::_::Debug::isWin32Success(call)) {} else \
if (auto _kjWin32Result = ::kj::_::Debug::win32Call(call)) {} else \
for (::kj::_::Debug::Fault f(__FILE__, __LINE__, \
::kj::_::Debug::getWin32Error(), #call, "" #__VA_ARGS__, __VA_ARGS__);; f.fatal())
_kjWin32Result, #call, "" #__VA_ARGS__, __VA_ARGS__);; f.fatal())
#define KJ_WINSOCK(call, ...) \
if ((call) != SOCKET_ERROR) {} else \
if (auto _kjWin32Result = ::kj::_::Debug::winsockCall(call)) {} else \
for (::kj::_::Debug::Fault f(__FILE__, __LINE__, \
::kj::_::Debug::getWin32Error(), #call, "" #__VA_ARGS__, __VA_ARGS__);; f.fatal())
_kjWin32Result, #call, "" #__VA_ARGS__, __VA_ARGS__);; f.fatal())
#define KJ_FAIL_WIN32(code, errorNumber, ...) \
for (::kj::_::Debug::Fault f(__FILE__, __LINE__, \
::kj::_::Debug::Win32Error(errorNumber), code, "" #__VA_ARGS__, __VA_ARGS__);; f.fatal())
::kj::_::Debug::Win32Result(errorNumber), code, "" #__VA_ARGS__, __VA_ARGS__);; f.fatal())
#endif
......@@ -247,18 +247,23 @@ namespace kj {
#if _WIN32
#define KJ_WIN32(call, ...) \
if (::kj::_::Debug::isWin32Success(call)) {} else \
if (auto _kjWin32Result = ::kj::_::Debug::win32Call(call)) {} else \
for (::kj::_::Debug::Fault f(__FILE__, __LINE__, \
::kj::_::Debug::getWin32Error(), #call, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
_kjWin32Result, #call, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
// Invoke a Win32 syscall that returns either BOOL or HANDLE, and throw an exception if it fails.
#define KJ_WINSOCK(call, ...) \
if ((call) != SOCKET_ERROR) {} else \
if (auto _kjWin32Result = ::kj::_::Debug::winsockCall(call)) {} else \
for (::kj::_::Debug::Fault f(__FILE__, __LINE__, \
::kj::_::Debug::getWin32Error(), #call, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
_kjWin32Result, #call, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
// Like KJ_WIN32 but for winsock calls which return `int` with SOCKET_ERROR indicating failure.
//
// Unfortunately, it's impossible to distinguish these from BOOL-returning Win32 calls by type,
// since BOOL is in fact an alias for `int`. :(
#define KJ_FAIL_WIN32(code, errorNumber, ...) \
for (::kj::_::Debug::Fault f(__FILE__, __LINE__, \
::kj::_::Debug::Win32Error(errorNumber), code, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
::kj::_::Debug::Win32Result(errorNumber), code, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
#endif
......@@ -292,7 +297,7 @@ namespace kj {
#define KJ_SYSCALL_HANDLE_ERRORS(call) \
if (int _kjSyscallError = ::kj::_::Debug::syscallError([&](){return (call);}, false)) \
switch (int error = _kjSyscallError)
switch (int error KJ_UNUSED = _kjSyscallError)
// Like KJ_SYSCALL, but doesn't throw. Instead, the block after the macro is a switch block on the
// error. Additionally, the int value `error` is defined within the block. So you can do:
//
......@@ -309,6 +314,29 @@ namespace kj {
// handleSuccessCase();
// }
#if _WIN32
#define KJ_WIN32_HANDLE_ERRORS(call) \
if (uint _kjWin32Error = ::kj::_::Debug::win32Call(call).number) \
switch (uint error KJ_UNUSED = _kjWin32Error)
// Like KJ_WIN32, but doesn't throw. Instead, the block after the macro is a switch block on the
// error. Additionally, the int value `error` is defined within the block. So you can do:
//
// KJ_SYSCALL_HANDLE_ERRORS(foo()) {
// case ERROR_FILE_NOT_FOUND:
// handleNoSuchFile();
// break;
// case ERROR_FILE_EXISTS:
// handleExists();
// break;
// default:
// KJ_FAIL_WIN32("foo()", error);
// } else {
// handleSuccessCase();
// }
#endif
#define KJ_ASSERT KJ_REQUIRE
#define KJ_FAIL_ASSERT KJ_FAIL_REQUIRE
#define KJ_ASSERT_NONNULL KJ_REQUIRE_NONNULL
......@@ -334,10 +362,10 @@ public:
typedef LogSeverity Severity; // backwards-compatibility
#if _WIN32
struct Win32Error {
// Hack for overloading purposes.
struct Win32Result {
uint number;
inline explicit Win32Error(uint number): number(number) {}
inline explicit Win32Result(uint number): number(number) {}
operator bool() const { return number == 0; }
};
#endif
......@@ -363,7 +391,7 @@ public:
Fault(const char* file, int line, int osErrorNumber,
const char* condition, const char* macroArgs);
#if _WIN32
Fault(const char* file, int line, Win32Error osErrorNumber,
Fault(const char* file, int line, Win32Result osErrorNumber,
const char* condition, const char* macroArgs);
#endif
~Fault() noexcept(false);
......@@ -377,7 +405,7 @@ public:
void init(const char* file, int line, int osErrorNumber,
const char* condition, const char* macroArgs, ArrayPtr<String> argValues);
#if _WIN32
void init(const char* file, int line, Win32Error osErrorNumber,
void init(const char* file, int line, Win32Result osErrorNumber,
const char* condition, const char* macroArgs, ArrayPtr<String> argValues);
#endif
......@@ -400,9 +428,10 @@ public:
static int syscallError(Call&& call, bool nonblocking);
#if _WIN32
static bool isWin32Success(int boolean);
static bool isWin32Success(void* handle);
static Win32Error getWin32Error();
static Win32Result win32Call(int boolean);
static Win32Result win32Call(void* handle);
static Win32Result winsockCall(int result);
static uint getWin32ErrorCode();
#endif
class Context: public ExceptionCallback {
......@@ -495,18 +524,22 @@ inline Debug::Fault::Fault(const char* file, int line, kj::Exception::Type type,
}
#if _WIN32
inline Debug::Fault::Fault(const char* file, int line, Win32Error osErrorNumber,
inline Debug::Fault::Fault(const char* file, int line, Win32Result osErrorNumber,
const char* condition, const char* macroArgs)
: exception(nullptr) {
init(file, line, osErrorNumber, condition, macroArgs, nullptr);
}
inline bool Debug::isWin32Success(int boolean) {
return boolean;
inline Debug::Win32Result Debug::win32Call(int boolean) {
return boolean ? Win32Result(0) : Win32Result(getWin32ErrorCode());
}
inline bool Debug::isWin32Success(void* handle) {
inline Debug::Win32Result Debug::win32Call(void* handle) {
// Assume null and INVALID_HANDLE_VALUE mean failure.
return handle != nullptr && handle != (void*)-1;
return win32Call(handle != nullptr && handle != (void*)-1);
}
inline Debug::Win32Result Debug::winsockCall(int result) {
// Expect a return value of SOCKET_ERROR means failure.
return win32Call(result != -1);
}
#endif
......
......@@ -30,6 +30,7 @@ CappedArray<char, sizeof(char ) * 2 + 1> hex(byte i) { return kj::hex((ui
CappedArray<char, sizeof(char ) * 2 + 1> hex(char i) { return kj::hex((uint8_t )i); }
CappedArray<char, sizeof(char16_t) * 2 + 1> hex(char16_t i) { return kj::hex((uint16_t)i); }
CappedArray<char, sizeof(char32_t) * 2 + 1> hex(char32_t i) { return kj::hex((uint32_t)i); }
CappedArray<char, sizeof(uint32_t) * 2 + 1> hex(wchar_t i) { return kj::hex((uint32_t)i); }
// Hexify chars correctly.
//
// TODO(cleanup): Should this go into string.h with the other definitions of hex()?
......@@ -64,6 +65,13 @@ void expectRes(EncodingResult<T> result,
expectResImpl(kj::mv(result), arrayPtr<const byte>(expected, s), errors);
}
// Handy reference for surrogate pair edge cases:
//
// \ud800 -> \xed\xa0\x80
// \udc00 -> \xed\xb0\x80
// \udbff -> \xed\xaf\xbf
// \udfff -> \xed\xbf\xbf
KJ_TEST("encode UTF-8 to UTF-16") {
expectRes(encodeUtf16(u8"foo"), u"foo");
expectRes(encodeUtf16(u8"Здравствуйте"), u"Здравствуйте");
......@@ -113,6 +121,21 @@ KJ_TEST("invalid UTF-8 to UTF-16") {
expectRes(encodeUtf16("\xfc\xbf\x80\x80\x80\x80"), u"\ufffd", true);
expectRes(encodeUtf16("\xfe\xbf\x80\x80\x80\x80\x80"), u"\ufffd", true);
expectRes(encodeUtf16("\xff\xbf\x80\x80\x80\x80\x80\x80"), u"\ufffd", true);
// Surrogates encoded as separate UTF-8 code points are flagged as errors but allowed to decode
// to UTF-16 surrogate values.
expectRes(encodeUtf16("\xed\xb0\x80\xed\xaf\xbf"), u"\xdc00\xdbff", true);
expectRes(encodeUtf16("\xed\xbf\xbf\xed\xa0\x80"), u"\xdfff\xd800", true);
expectRes(encodeUtf16("\xed\xb0\x80\xed\xbf\xbf"), u"\xdc00\xdfff", true);
expectRes(encodeUtf16("f\xed\xa0\x80"), u"f\xd800", true);
expectRes(encodeUtf16("f\xed\xa0\x80x"), u"f\xd800x", true);
expectRes(encodeUtf16("f\xed\xa0\x80\xed\xa0\x80x"), u"f\xd800\xd800x", true);
// However, if successive UTF-8 codepoints decode to a proper surrogate pair, the second
// surrogate is replaced with the Unicode replacement character to avoid creating valid UTF-16.
expectRes(encodeUtf16("\xed\xa0\x80\xed\xbf\xbf"), u"\xd800\xfffd", true);
expectRes(encodeUtf16("\xed\xaf\xbf\xed\xb0\x80"), u"\xdbff\xfffd", true);
}
KJ_TEST("encode UTF-8 to UTF-32") {
......@@ -169,12 +192,15 @@ KJ_TEST("decode UTF-16 to UTF-8") {
KJ_TEST("invalid UTF-16 to UTF-8") {
// Surrogates in wrong order.
expectRes(decodeUtf16(u"\xd7ff\xdc00\xdfff\xe000"), u8"\ud7ff\ufffd\ufffd\ue000", true);
expectRes(decodeUtf16(u"\xdc00\xdbff"),
"\xed\xb0\x80\xed\xaf\xbf", true);
expectRes(decodeUtf16(u"\xdfff\xd800"),
"\xed\xbf\xbf\xed\xa0\x80", true);
// Missing second surrogate.
expectRes(decodeUtf16(u"f\xd800"), u8"f\ufffd", true);
expectRes(decodeUtf16(u"f\xd800x"), u8"f\ufffdx", true);
expectRes(decodeUtf16(u"f\xd800\xd800x"), u8"f\ufffd\ufffdx", true);
expectRes(decodeUtf16(u"f\xd800"), "f\xed\xa0\x80", true);
expectRes(decodeUtf16(u"f\xd800x"), "f\xed\xa0\x80x", true);
expectRes(decodeUtf16(u"f\xd800\xd800x"), "f\xed\xa0\x80\xed\xa0\x80x", true);
}
KJ_TEST("decode UTF-32 to UTF-8") {
......@@ -186,10 +212,19 @@ KJ_TEST("decode UTF-32 to UTF-8") {
KJ_TEST("invalid UTF-32 to UTF-8") {
// Surrogates rejected.
expectRes(decodeUtf32(U"\xd7ff\xdc00\xdfff\xe000"), u8"\ud7ff\ufffd\ufffd\ue000", true);
expectRes(decodeUtf32(U"\xdfff\xd800"),
"\xed\xbf\xbf\xed\xa0\x80", true);
// Even if it would be a valid surrogate pair in UTF-16.
expectRes(decodeUtf32(U"\xd7ff\xd800\xdfff\xe000"), u8"\ud7ff\ufffd\ufffd\ue000", true);
expectRes(decodeUtf32(U"\xd800\xdfff"),
"\xed\xa0\x80\xed\xbf\xbf", true);
}
KJ_TEST("round-trip invalid UTF-16") {
const char16_t INVALID[] = u"\xdfff foo \xd800\xdc00 bar \xdc00\xd800 baz \xdbff qux \xd800";
expectRes(encodeUtf16(decodeUtf16(INVALID)), INVALID, true);
expectRes(encodeUtf16(decodeUtf32(encodeUtf32(decodeUtf16(INVALID)))), INVALID, true);
}
KJ_TEST("EncodingResult as a Maybe") {
......@@ -206,6 +241,20 @@ KJ_TEST("EncodingResult as a Maybe") {
KJ_EXPECT(KJ_ASSERT_NONNULL(decodeUtf16(u"foo")) == "foo");
}
KJ_TEST("encode to wchar_t") {
expectRes(encodeWideString(u8"foo"), L"foo");
expectRes(encodeWideString(u8"Здравствуйте"), L"Здравствуйте");
expectRes(encodeWideString(u8"中国网络"), L"中国网络");
expectRes(encodeWideString(u8"😺☁☄🐵"), L"😺☁☄🐵");
}
KJ_TEST("decode from wchar_t") {
expectRes(decodeWideString(L"foo"), u8"foo");
expectRes(decodeWideString(L"Здравствуйте"), u8"Здравствуйте");
expectRes(decodeWideString(L"中国网络"), u8"中国网络");
expectRes(decodeWideString(L"😺☁☄🐵"), u8"😺☁☄🐵");
}
// =======================================================================================
KJ_TEST("hex encoding/decoding") {
......
......@@ -79,8 +79,23 @@ EncodingResult<Array<T>> encodeUtf(ArrayPtr<const char> text, bool nulTerminate)
// Disallow overlong sequence.
GOTO_ERROR_IF(u < 0x0800);
// Disallow surrogate pair code points.
GOTO_ERROR_IF((u & 0xf800) == 0xd800);
// Flag surrogate pair code points as errors, but allow them through.
if (KJ_UNLIKELY((u & 0xf800) == 0xd800)) {
if (result.size() > 0 &&
(u & 0xfc00) == 0xdc00 &&
(result.back() & 0xfc00) == 0xd800) {
// Whoops, the *previous* character was also an invalid surrogate, and if we add this
// one too, they'll form a valid surrogate pair. If we allowed this, then it would mean
// invalid UTF-8 round-tripped to UTF-16 and back could actually change meaning entirely.
// OTOH, the reason we allow dangling surrogates is to allow invalid UTF-16 to round-trip
// to UTF-8 without loss, but if the original UTF-16 had a valid surrogate pair, it would
// have been encoded as a valid single UTF-8 codepoint, not as separate UTF-8 codepoints
// for each surrogate.
goto error;
}
hadErrors = true;
}
result.add(u);
continue;
......@@ -153,9 +168,12 @@ EncodingResult<String> decodeUtf16(ArrayPtr<const char16_t> utf16) {
} else if ((u & 0xf800) == 0xd800) {
// surrogate pair
char16_t u2;
GOTO_ERROR_IF(i == utf16.size() // missing second half
|| (u & 0x0400) != 0 // first half in wrong range
|| ((u2 = utf16[i]) & 0xfc00) != 0xdc00); // second half in wrong range
if (KJ_UNLIKELY(i == utf16.size() // missing second half
|| (u & 0x0400) != 0 // first half in wrong range
|| ((u2 = utf16[i]) & 0xfc00) != 0xdc00)) { // second half in wrong range
hadErrors = true;
goto threeByte;
}
++i;
char32_t u32 = (((u & 0x03ff) << 10) | (u2 & 0x03ff)) + 0x10000;
......@@ -167,6 +185,7 @@ EncodingResult<String> decodeUtf16(ArrayPtr<const char16_t> utf16) {
});
continue;
} else {
threeByte:
result.addAll<std::initializer_list<char>>({
static_cast<char>(((u >> 12) ) | 0xe0),
static_cast<char>(((u >> 6) & 0x3f) | 0x80),
......@@ -174,10 +193,6 @@ EncodingResult<String> decodeUtf16(ArrayPtr<const char16_t> utf16) {
});
continue;
}
error:
result.addAll(StringPtr(u8"\ufffd"));
hadErrors = true;
}
result.add(0);
......@@ -202,7 +217,10 @@ EncodingResult<String> decodeUtf32(ArrayPtr<const char32_t> utf16) {
});
continue;
} else if (u < 0x10000) {
GOTO_ERROR_IF((u & 0xfffff800) == 0xd800); // no surrogates allowed in utf-32
if (KJ_UNLIKELY((u & 0xfffff800) == 0xd800)) {
// no surrogates allowed in utf-32
hadErrors = true;
}
result.addAll<std::initializer_list<char>>({
static_cast<char>(((u >> 12) ) | 0xe0),
static_cast<char>(((u >> 6) & 0x3f) | 0x80),
......@@ -229,6 +247,85 @@ EncodingResult<String> decodeUtf32(ArrayPtr<const char32_t> utf16) {
return { String(result.releaseAsArray()), hadErrors };
}
namespace {
template <typename To, typename From>
Array<To> coerceTo(Array<From>&& array) {
static_assert(sizeof(To) == sizeof(From), "incompatible coercion");
Array<wchar_t> result;
memcpy(&result, &array, sizeof(array));
memset(&array, 0, sizeof(array));
return result;
}
template <typename To, typename From>
ArrayPtr<To> coerceTo(ArrayPtr<From> array) {
static_assert(sizeof(To) == sizeof(From), "incompatible coercion");
return arrayPtr(reinterpret_cast<To*>(array.begin()), array.size());
}
template <typename To, typename From>
EncodingResult<Array<To>> coerceTo(EncodingResult<Array<From>>&& result) {
return { coerceTo<To>(Array<From>(kj::mv(result))), result.hadErrors };
}
template <size_t s>
struct WideConverter;
template <>
struct WideConverter<sizeof(char)> {
typedef char Type;
static EncodingResult<Array<char>> encode(ArrayPtr<const char> text, bool nulTerminate) {
auto result = heapArray<char>(text.size() + nulTerminate);
memcpy(result.begin(), text.begin(), text.size());
if (nulTerminate) result.back() = 0;
return { kj::mv(result), false };
}
static EncodingResult<kj::String> decode(ArrayPtr<const char> text) {
return { kj::heapString(text), false };
}
};
template <>
struct WideConverter<sizeof(char16_t)> {
typedef char16_t Type;
static inline EncodingResult<Array<char16_t>> encode(
ArrayPtr<const char> text, bool nulTerminate) {
return encodeUtf16(text, nulTerminate);
}
static inline EncodingResult<kj::String> decode(ArrayPtr<const char16_t> text) {
return decodeUtf16(text);
}
};
template <>
struct WideConverter<sizeof(char32_t)> {
typedef char32_t Type;
static inline EncodingResult<Array<char32_t>> encode(
ArrayPtr<const char> text, bool nulTerminate) {
return encodeUtf32(text, nulTerminate);
}
static inline EncodingResult<kj::String> decode(ArrayPtr<const char32_t> text) {
return decodeUtf32(text);
}
};
} // namespace
EncodingResult<Array<wchar_t>> encodeWideString(ArrayPtr<const char> text, bool nulTerminate) {
return coerceTo<wchar_t>(WideConverter<sizeof(wchar_t)>::encode(text, nulTerminate));
}
EncodingResult<String> decodeWideString(ArrayPtr<const wchar_t> wide) {
using Converter = WideConverter<sizeof(wchar_t)>;
return Converter::decode(coerceTo<const Converter::Type>(wide));
}
// =======================================================================================
namespace {
......
......@@ -52,17 +52,24 @@ struct EncodingResult: public ResultType {
const bool hadErrors;
};
template <typename T>
inline auto KJ_STRINGIFY(const EncodingResult<T>& value)
-> decltype(toCharSequence(implicitCast<const T&>(value))) {
return toCharSequence(implicitCast<const T&>(value));
}
EncodingResult<Array<char16_t>> encodeUtf16(ArrayPtr<const char> text, bool nulTerminate = false);
EncodingResult<Array<char32_t>> encodeUtf32(ArrayPtr<const char> text, bool nulTerminate = false);
// Convert UTF-8 text (which KJ strings use) to UTF-16 or UTF-32.
//
// If `nulTerminate` is true, an extra NUL character will be added to the end of the output.
//
// The `try` versions return null if the input is invalid; the non-`try` versions return data
// containing the Unicode replacement character (U+FFFD).
//
// The returned arrays are in platform-native endianness (otherwise they wouldn't really be
// char16_t / char32_t).
//
// Note that the KJ Unicode encoding and decoding functions actually implement
// [WTF-8 encoding](http://simonsapin.github.io/wtf-8/), which affects how invalid input is
// handled. See comments on decodeUtf16() for more info.
EncodingResult<String> decodeUtf16(ArrayPtr<const char16_t> utf16);
EncodingResult<String> decodeUtf32(ArrayPtr<const char32_t> utf32);
......@@ -71,10 +78,46 @@ EncodingResult<String> decodeUtf32(ArrayPtr<const char32_t> utf32);
// The input should NOT include a NUL terminator; any NUL characters in the input array will be
// preserved in the output.
//
// The `try` versions return null if the input is invalid; the non-`try` versions return data
// containing the Unicode replacement character (U+FFFD).
//
// The input must be in platform-native endianness. BOMs are NOT recognized by these functions.
//
// Note that the KJ Unicode encoding and decoding functions actually implement
// [WTF-8 encoding](http://simonsapin.github.io/wtf-8/). This means that if you start with an array
// of char16_t and you pass it through any number of conversions to other Unicode encodings,
// eventually returning it to UTF-16, all the while ignoring `hadErrors`, you will end up with
// exactly the same char16_t array you started with, *even if* the array is not valid UTF-16. This
// is useful because many real-world systems that were designed for UCS-2 (plain 16-bit Unicode)
// and later "upgraded" to UTF-16 do not enforce that their UTF-16 is well-formed. For example,
// file names on Windows NT are encoded using 16-bit characters, without enforcing that the
// character sequence is valid UTF-16. It is important that programs on Windows be able to handle
// such filenames, even if they choose to convert the name to UTF-8 for internal processing.
//
// Specifically, KJ's Unicode handling allows unpaired surrogate code points to round-trip through
// UTF-8 and UTF-32. Unpaired surrogates will be flagged as an error (setting `hadErrors` in the
// result), but will NOT be replaced with the Unicode replacement character as other erroneous
// sequences would be, but rather encoded as an invalid surrogate codepoint in the target encoding.
//
// KJ makes the following guarantees about invalid input:
// - A round trip from UTF-16 to other encodings and back will produce exactly the original input,
// with every leg of the trip raising the `hadErrors` flag if the original input was not valid.
// - A round trip from UTF-8 or UTF-32 to other encodings and back will either produce exactly
// the original input, or will have replaced some invalid sequences with the Unicode replacement
// character, U+FFFD. No code units will ever be removed unless they are replaced with U+FFFD,
// and no code units will ever be added except to encode U+FFFD. If the original input was not
// valid, the `hadErrors` flag will be raised on the first leg of the trip, and will also be
// raised on subsequent legs unless all invalid sequences were replaced with U+FFFD (which, after
// all, is a valid code point).
EncodingResult<Array<wchar_t>> encodeWideString(
ArrayPtr<const char> text, bool nulTerminate = false);
EncodingResult<String> decodeWideString(ArrayPtr<const wchar_t> wide);
// Encode / decode strings of wchar_t, aka "wide strings". Unfortunately, different platforms have
// different definitions for wchar_t. For example, on Windows they are 16-bit and encode UTF-16,
// but on Linux they are 32-bit and encode UTF-32. Some platforms even define wchar_t as 8-bit,
// encoding UTF-8 (e.g. BeOS did this).
//
// KJ assumes that wide strings use the UTF encoding that corresponds to the size of wchar_t on
// the target platform. So, these functions are simple aliases for encodeUtf*/decodeUtf*, above
// (or simply make a copy if wchar_t is 8 bits).
String encodeHex(ArrayPtr<const byte> bytes);
EncodingResult<Array<byte>> decodeHex(ArrayPtr<const char> text);
......@@ -164,6 +207,11 @@ inline EncodingResult<Array<char32_t>> encodeUtf32(const char (&text)[s], bool n
return encodeUtf32(arrayPtr(text, s - 1), nulTerminate);
}
template <size_t s>
inline EncodingResult<Array<wchar_t>> encodeWideString(
const char (&text)[s], bool nulTerminate=false) {
return encodeWideString(arrayPtr(text, s - 1), nulTerminate);
}
template <size_t s>
inline EncodingResult<String> decodeUtf16(const char16_t (&utf16)[s]) {
return decodeUtf16(arrayPtr(utf16, s - 1));
}
......@@ -172,6 +220,10 @@ inline EncodingResult<String> decodeUtf32(const char32_t (&utf32)[s]) {
return decodeUtf32(arrayPtr(utf32, s - 1));
}
template <size_t s>
inline EncodingResult<String> decodeWideString(const wchar_t (&utf32)[s]) {
return decodeWideString(arrayPtr(utf32, s - 1));
}
template <size_t s>
inline EncodingResult<Array<byte>> decodeHex(const char (&text)[s]) {
return decodeHex(arrayPtr(text, s - 1));
}
......
......@@ -152,7 +152,9 @@ ArrayPtr<void* const> getStackTrace(ArrayPtr<void*> space, uint ignoreCount,
break;
}
space[count] = reinterpret_cast<void*>(frame.AddrPC.Offset);
// Subtract 1 from each address so that we identify the calling instructions, rather than the
// return addresses (which are typically the instruction after the call).
space[count] = reinterpret_cast<void*>(frame.AddrPC.Offset - 1);
}
return space.slice(kj::min(ignoreCount, count), count);
......@@ -346,7 +348,7 @@ BOOL WINAPI breakHandler(DWORD type) {
context.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(thread, &context)) {
void* traceSpace[32];
auto trace = getStackTrace(traceSpace, 2, thread, context);
auto trace = getStackTrace(traceSpace, 0, thread, context);
ResumeThread(thread);
auto message = kj::str("*** Received CTRL+C. stack: ",
stringifyStackTraceAddresses(trace),
......@@ -367,11 +369,51 @@ BOOL WINAPI breakHandler(DWORD type) {
return FALSE; // still crash
}
kj::StringPtr exceptionDescription(DWORD code) {
switch (code) {
case EXCEPTION_ACCESS_VIOLATION: return "access violation";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "array bounds exceeded";
case EXCEPTION_BREAKPOINT: return "breakpoint";
case EXCEPTION_DATATYPE_MISALIGNMENT: return "datatype misalignment";
case EXCEPTION_FLT_DENORMAL_OPERAND: return "denormal floating point operand";
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "floating point division by zero";
case EXCEPTION_FLT_INEXACT_RESULT: return "inexact floating point result";
case EXCEPTION_FLT_INVALID_OPERATION: return "invalid floating point operation";
case EXCEPTION_FLT_OVERFLOW: return "floating point overflow";
case EXCEPTION_FLT_STACK_CHECK: return "floating point stack overflow";
case EXCEPTION_FLT_UNDERFLOW: return "floating point underflow";
case EXCEPTION_ILLEGAL_INSTRUCTION: return "illegal instruction";
case EXCEPTION_IN_PAGE_ERROR: return "page error";
case EXCEPTION_INT_DIVIDE_BY_ZERO: return "integer divided by zero";
case EXCEPTION_INT_OVERFLOW: return "integer overflow";
case EXCEPTION_INVALID_DISPOSITION: return "invalid disposition";
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "noncontinuable exception";
case EXCEPTION_PRIV_INSTRUCTION: return "privileged instruction";
case EXCEPTION_SINGLE_STEP: return "single step";
case EXCEPTION_STACK_OVERFLOW: return "stack overflow";
default: return "(unknown exception code)";
}
}
LONG WINAPI sehHandler(EXCEPTION_POINTERS* info) {
void* traceSpace[32];
auto trace = getStackTrace(traceSpace, 0, GetCurrentThread(), *info->ContextRecord);
auto message = kj::str("*** Received structured exception #0x",
hex(info->ExceptionRecord->ExceptionCode), ": ",
exceptionDescription(info->ExceptionRecord->ExceptionCode),
"; stack: ",
stringifyStackTraceAddresses(trace),
stringifyStackTrace(trace), '\n');
FdOutputStream(STDERR_FILENO).write(message.begin(), message.size());
return EXCEPTION_EXECUTE_HANDLER; // still crash
}
} // namespace
void printStackTraceOnCrash() {
mainThreadId = GetCurrentThreadId();
KJ_WIN32(SetConsoleCtrlHandler(breakHandler, TRUE));
SetUnhandledExceptionFilter(&sehHandler);
}
#elif KJ_HAS_BACKTRACE
......
......@@ -19,8 +19,18 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// This test compiles filesystem-disk.c++ with various features #undefed, causing it to take
#if __linux__
// This test compiles filesystem-disk-unix.c++ with various features #undefed, causing it to take
// different code paths, then runs filesystem-disk-test.c++ against that.
//
// This test is only intended to run on Linux, but is intended to make the code behave like it
// would on a generic flavor of Unix.
//
// At present this test only runs under Ekam builds. Integrating it into other builds would be
// awkward since it #includes filesystem-disk-unix.c++, so it cannot link against that file, but
// needs to link against the rest of KJ. Ekam "just figures it out", but other build systems would
// require a lot of work here.
#include "filesystem.h"
#include "debug.h"
......@@ -53,5 +63,7 @@
#define HOLES_NOT_SUPPORTED
#include "filesystem-disk.c++"
#include "filesystem-disk-unix.c++"
#include "filesystem-disk-test.c++"
#endif // __linux__
......@@ -29,6 +29,11 @@
//
// This test must be compiled as a separate program, since it alters the calling process by
// enabling seccomp to disable the kernel features.
//
// At present this test only runs under Ekam builds. It *could* reasonably easily be added to the
// autotools or cmake builds, but would require compiling a separate test binary, which is a bit
// weird, and may lead to spurious error reports on systems that don't support seccomp for whatever
// reason.
#include <syscall.h>
#include <unistd.h>
......
This diff is collapsed.
......@@ -19,6 +19,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#if !_WIN32
#include "filesystem.h"
#include "debug.h"
#include <sys/types.h>
......@@ -30,13 +32,13 @@
#include <sys/mman.h>
#include <errno.h>
#include <dirent.h>
#include <syscall.h>
#include <stdlib.h>
#include "vector.h"
#include "miniposix.h"
#include <algorithm>
#if __linux__
#include <syscall.h>
#include <linux/fs.h>
#include <sys/sendfile.h>
#endif
......@@ -61,6 +63,11 @@ namespace {
#define MAYBE_O_DIRECTORY 0
#endif
#if __APPLE__
// Mac OSX defines SEEK_HOLE, but it doesn't work. ("Inappropriate ioctl for device", it says.)
#undef SEEK_HOLE
#endif
static void setCloexec(int fd) KJ_UNUSED;
static void setCloexec(int fd) {
// Set the O_CLOEXEC flag on the given fd.
......@@ -117,7 +124,11 @@ static FsNode::Metadata statToMetadata(struct stat& stats) {
modeToType(stats.st_mode),
implicitCast<uint64_t>(stats.st_size),
implicitCast<uint64_t>(stats.st_blocks * 512u),
#if __APPLE__
toKjDate(stats.st_mtimespec),
#else
toKjDate(stats.st_mtim),
#endif
implicitCast<uint>(stats.st_nlink)
};
}
......@@ -223,12 +234,7 @@ protected:
size_t capacity, void (*destroyElement)(void*)) const {
auto range = getMmapRange(reinterpret_cast<uintptr_t>(firstElement),
elementSize * elementCount);
#if _WIN32
KJ_ASSERT(UnmapViewOfFile(reinterpret_cast<byte*>(range.offset))) { break; }
#else
KJ_SYSCALL(munmap(reinterpret_cast<byte*>(range.offset), range.size)) { break; }
#endif
}
};
......@@ -283,8 +289,26 @@ public:
return statToMetadata(stats);
}
void sync() { KJ_SYSCALL(fsync(fd)); }
void datasync() { KJ_SYSCALL(fdatasync(fd)); }
void sync() {
#if __APPLE__
// For whatever reason, fsync() on OSX only flushes kernel buffers. It does not flush hardware
// disk buffers. This makes it not very useful. But OSX documents fcntl F_FULLFSYNC which does
// the right thing. Why they don't just make fsync() do the right thing, I do not know.
KJ_SYSCALL(fcntl(fd, F_FULLFSYNC));
#else
KJ_SYSCALL(fsync(fd));
#endif
}
void datasync() {
// The presence of the _POSIX_SYNCHRONIZED_IO define is supposed to tell us that fdatasync()
// exists. But Apple defines this yet doesn't offer fdatasync(). Thanks, Apple.
#if _POSIX_SYNCHRONIZED_IO && !__APPLE__
KJ_SYSCALL(fdatasync(fd));
#else
this->sync();
#endif
}
// ReadableFile --------------------------------------------------------------
......@@ -353,26 +377,34 @@ public:
}
#endif
// Use a 4k buffer of zeros amplified by iov to write zeros with as few syscalls as possible.
byte buffer[4096];
memset(buffer, 0, sizeof(buffer));
static const byte ZEROS[4096] = { 0 };
size_t count = (size + sizeof(buffer) - 1) / sizeof(buffer);
#if __APPLE__
// Mac doesn't have pwritev().
while (size > sizeof(ZEROS)) {
write(offset, ZEROS);
size -= sizeof(ZEROS);
offset += sizeof(ZEROS);
}
write(offset, kj::arrayPtr(ZEROS, size));
#else
// Use a 4k buffer of zeros amplified by iov to write zeros with as few syscalls as possible.
size_t count = (size + sizeof(ZEROS) - 1) / sizeof(ZEROS);
const size_t iovmax = miniposix::iovMax(count);
KJ_STACK_ARRAY(struct iovec, iov, kj::min(iovmax, count), 16, 256);
for (auto& item: iov) {
item.iov_base = buffer;
item.iov_len = sizeof(buffer);
item.iov_base = const_cast<byte*>(ZEROS);
item.iov_len = sizeof(ZEROS);
}
while (size > 0) {
size_t iovCount;
if (size >= iov.size() * sizeof(buffer)) {
if (size >= iov.size() * sizeof(ZEROS)) {
iovCount = iov.size();
} else {
iovCount = size / sizeof(buffer);
size_t rem = size % sizeof(buffer);
iovCount = size / sizeof(ZEROS);
size_t rem = size % sizeof(ZEROS);
if (rem > 0) {
iov[iovCount++].iov_len = rem;
}
......@@ -385,13 +417,14 @@ public:
offset += n;
size -= n;
}
#endif
}
void truncate(uint64_t size) {
KJ_SYSCALL(ftruncate(fd, size));
}
class WritableFileMappingImpl: public WritableFileMapping {
class WritableFileMappingImpl final: public WritableFileMapping {
public:
WritableFileMappingImpl(Array<byte> bytes): bytes(kj::mv(bytes)) {}
......@@ -1016,7 +1049,7 @@ public:
}
}
#ifdef RENAME_EXCHANGE
#if __linux__ && defined(RENAME_EXCHANGE)
// Try to use Linux's renameat2() to atomically check preconditions and apply.
if (has(mode, WriteMode::MODIFY)) {
......@@ -1096,7 +1129,15 @@ public:
if (S_ISDIR(stats.st_mode)) {
return mkdirat(fd, candidatePath.cStr(), 0700);
} else {
#if __APPLE__
// No mknodat() on OSX, gotta open() a file, ugh.
int newFd = openat(fd, candidatePath.cStr(),
O_RDWR | O_CREAT | O_EXCL | MAYBE_O_CLOEXEC, 0700);
if (newFd >= 0) close(newFd);
return newFd;
#else
return mknodat(fd, candidatePath.cStr(), S_IFREG | 0600, dev_t());
#endif
}
})) {
away = kj::mv(*awayPath);
......@@ -1172,7 +1213,7 @@ public:
}
template <typename T>
class ReplacerImpl: public Directory::Replacer<T> {
class ReplacerImpl final: public Directory::Replacer<T> {
public:
ReplacerImpl(Own<T>&& object, DiskHandle& handle,
String&& tempPath, String&& path, WriteMode mode)
......@@ -1205,7 +1246,7 @@ public:
};
template <typename T>
class BrokenReplacer: public Directory::Replacer<T> {
class BrokenReplacer final: public Directory::Replacer<T> {
// For recovery path when exceptions are disabled.
public:
......@@ -1254,7 +1295,7 @@ public:
Own<File> createTemporary() {
int newFd_;
#ifdef O_TMPFILE
#if __linux__ && defined(O_TMPFILE)
// Use syscall() to work around glibc bug with O_TMPFILE:
// https://sourceware.org/bugzilla/show_bug.cgi?id=17523
KJ_SYSCALL_HANDLE_ERRORS(newFd_ = syscall(
......@@ -1655,3 +1696,5 @@ Own<Filesystem> newDiskFilesystem() {
}
} // namespace kj
#endif // !_WIN32
This diff is collapsed.
......@@ -21,6 +21,7 @@
#include "filesystem.h"
#include "test.h"
#include <wchar.h>
namespace kj {
namespace {
......@@ -108,6 +109,14 @@ KJ_TEST("Path exceptions") {
KJ_EXPECT_THROW_MESSAGE("root path has no parent", Path(nullptr).parent());
}
static inline bool operator==(const Array<wchar_t>& arr, const wchar_t* expected) {
return wcscmp(arr.begin(), expected) == 0;
}
constexpr kj::ArrayPtr<const wchar_t> operator "" _a(const wchar_t* str, size_t n) {
return { str, n };
}
KJ_TEST("Win32 Path") {
KJ_EXPECT(Path({"foo", "bar"}).toWin32String() == "foo\\bar");
KJ_EXPECT(Path({"foo", "bar"}).toWin32String(true) == "\\\\foo\\bar");
......@@ -147,6 +156,16 @@ KJ_TEST("Win32 Path") {
.toWin32String(true) == "d:\\qux");
KJ_EXPECT(Path({"foo", "bar", "baz"}).evalWin32("\\qux")
.toWin32String(true) == "\\\\foo\\bar\\qux");
KJ_EXPECT(Path({"foo", "bar"}).forWin32Api(false) == L"foo\\bar");
KJ_EXPECT(Path({"foo", "bar"}).forWin32Api(true) == L"\\\\?\\UNC\\foo\\bar");
KJ_EXPECT(Path({"c:", "foo", "bar"}).forWin32Api(true) == L"\\\\?\\c:\\foo\\bar");
KJ_EXPECT(Path({"A:", "foo", "bar"}).forWin32Api(true) == L"\\\\?\\A:\\foo\\bar");
KJ_EXPECT(Path::parseWin32Api(L"\\\\?\\c:\\foo\\bar"_a).toString() == "c:/foo/bar");
KJ_EXPECT(Path::parseWin32Api(L"\\\\?\\UNC\\foo\\bar"_a).toString() == "foo/bar");
KJ_EXPECT(Path::parseWin32Api(L"c:\\foo\\bar"_a).toString() == "c:/foo/bar");
KJ_EXPECT(Path::parseWin32Api(L"\\\\foo\\bar"_a).toString() == "foo/bar");
}
KJ_TEST("Win32 Path exceptions") {
......
......@@ -23,6 +23,8 @@
#include "vector.h"
#include "debug.h"
#include "one-of.h"
#include "encoding.h"
#include "refcount.h"
#include <map>
namespace kj {
......@@ -54,13 +56,18 @@ Path Path::parse(StringPtr path) {
return evalImpl(Vector<String>(countParts(path)), path);
}
Path PathPtr::append(Path suffix) const {
Path Path::parseWin32Api(ArrayPtr<const wchar_t> text) {
auto utf8 = decodeWideString(text);
return evalWin32Impl(Vector<String>(countPartsWin32(utf8)), utf8, true);
}
Path PathPtr::append(Path&& suffix) const {
auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size());
for (auto& p: parts) newParts.add(heapString(p));
for (auto& p: suffix.parts) newParts.add(kj::mv(p));
return Path(newParts.finish(), Path::ALREADY_CHECKED);
}
Path Path::append(Path suffix) && {
Path Path::append(Path&& suffix) && {
auto newParts = kj::heapArrayBuilder<String>(parts.size() + suffix.parts.size());
for (auto& p: parts) newParts.add(kj::mv(p));
for (auto& p: suffix.parts) newParts.add(kj::mv(p));
......@@ -159,7 +166,7 @@ Path Path::evalWin32(StringPtr pathText) && {
return evalWin32Impl(kj::mv(newParts), pathText);
}
String PathPtr::toWin32String(bool absolute) const {
String PathPtr::toWin32StringImpl(bool absolute, bool forApi) const {
if (parts.size() == 0) {
// Special-case empty path.
KJ_REQUIRE(!absolute, "absolute path is missing disk designator") {
......@@ -178,18 +185,36 @@ String PathPtr::toWin32String(bool absolute) const {
KJ_FAIL_REQUIRE("absolute win32 path must start with drive letter or netbios host name",
parts[0]);
}
} else {
// Currently we do nothing differently in the forApi case for relative paths.
forApi = false;
}
size_t size = (isUncPath ? 2 : 0) + (parts.size() - 1);
size_t size = forApi
? (isUncPath ? 8 : 4) + (parts.size() - 1)
: (isUncPath ? 2 : 0) + (parts.size() - 1);
for (auto& p: parts) size += p.size();
String result = heapString(size);
char* ptr = result.begin();
if (isUncPath) {
if (forApi) {
*ptr++ = '\\';
*ptr++ = '\\';
*ptr++ = '?';
*ptr++ = '\\';
if (isUncPath) {
*ptr++ = 'U';
*ptr++ = 'N';
*ptr++ = 'C';
*ptr++ = '\\';
}
} else {
if (isUncPath) {
*ptr++ = '\\';
*ptr++ = '\\';
}
}
bool leadingSlash = false;
......@@ -217,7 +242,7 @@ String PathPtr::toWin32String(bool absolute) const {
// appearing to start with a drive letter.
for (size_t i: kj::indices(result)) {
if (result[i] == ':') {
if (absolute && i == 1) {
if (absolute && i == (forApi ? 5 : 1)) {
// False alarm: this is the drive letter.
} else {
KJ_FAIL_REQUIRE(
......@@ -233,6 +258,10 @@ String PathPtr::toWin32String(bool absolute) const {
return result;
}
Array<wchar_t> PathPtr::forWin32Api(bool absolute) const {
return encodeWideString(toWin32StringImpl(absolute, true), true);
}
// -----------------------------------------------------------------------------
String Path::stripNul(String input) {
......@@ -290,10 +319,10 @@ Path Path::evalImpl(Vector<String>&& parts, StringPtr path) {
return Path(parts.releaseAsArray(), Path::ALREADY_CHECKED);
}
Path Path::evalWin32Impl(Vector<String>&& parts, StringPtr path) {
Path Path::evalWin32Impl(Vector<String>&& parts, StringPtr path, bool fromApi) {
// Convert all forward slashes to backslashes.
String ownPath;
if (path.findFirst('/') != nullptr) {
if (!fromApi && path.findFirst('/') != nullptr) {
ownPath = heapString(path);
for (char& c: ownPath) {
if (c == '/') c = '\\';
......@@ -302,13 +331,23 @@ Path Path::evalWin32Impl(Vector<String>&& parts, StringPtr path) {
}
// Interpret various forms of absolute paths.
if (path.startsWith("\\\\")) {
if (fromApi && path.startsWith("\\\\?\\")) {
path = path.slice(4);
if (path.startsWith("UNC\\")) {
path = path.slice(4);
}
// The path is absolute.
parts.clear();
} else if (path.startsWith("\\\\")) {
// UNC path.
path = path.slice(2);
// This path is absolute. The first component is a server name.
parts.clear();
} else if (path.startsWith("\\")) {
KJ_REQUIRE(!fromApi, "parseWin32Api() requires absolute path");
// Path is relative to the current drive / network share.
if (parts.size() >= 1 && isWin32Drive(parts[0])) {
// Leading \ interpreted as root of current drive.
......@@ -329,6 +368,8 @@ Path Path::evalWin32Impl(Vector<String>&& parts, StringPtr path) {
isWin32Drive(path.slice(0, 2))) {
// Starts with a drive letter.
parts.clear();
} else {
KJ_REQUIRE(!fromApi, "parseWin32Api() requires absolute path");
}
size_t partStart = 0;
......@@ -671,6 +712,8 @@ bool Directory::tryTransfer(PathPtr toPath, WriteMode toMode,
case TransferMode::LINK:
KJ_FAIL_REQUIRE("can't link across different Directory implementations") { return false; }
}
KJ_UNREACHABLE;
}
Maybe<bool> Directory::tryTransferTo(Directory& toDirectory, PathPtr toPath, WriteMode toMode,
......@@ -1295,7 +1338,7 @@ private:
Date lastModified;
template <typename T>
class ReplacerImpl: public Replacer<T> {
class ReplacerImpl final: public Replacer<T> {
public:
ReplacerImpl(InMemoryDirectory& directory, kj::StringPtr name, Own<T> inner, WriteMode mode)
: Replacer<T>(mode), directory(addRef(directory)), name(heapString(name)),
......@@ -1323,7 +1366,7 @@ private:
};
template <typename T>
class BrokenReplacer: public Replacer<T> {
class BrokenReplacer final: public Replacer<T> {
// For recovery path when exceptions are disabled.
public:
......
This diff is collapsed.
......@@ -26,10 +26,6 @@
namespace kj {
kj::Exception Timer::makeTimeoutException() {
return KJ_EXCEPTION(OVERLOADED, "operation timed out");
}
Clock& nullClock() {
class NullClock final: public Clock {
public:
......@@ -39,96 +35,4 @@ Clock& nullClock() {
return NULL_CLOCK;
}
struct TimerImpl::Impl {
struct TimerBefore {
bool operator()(TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs);
};
using Timers = std::multiset<TimerPromiseAdapter*, TimerBefore>;
Timers timers;
};
class TimerImpl::TimerPromiseAdapter {
public:
TimerPromiseAdapter(PromiseFulfiller<void>& fulfiller, TimerImpl::Impl& impl, TimePoint time)
: time(time), fulfiller(fulfiller), impl(impl) {
pos = impl.timers.insert(this);
}
~TimerPromiseAdapter() {
if (pos != impl.timers.end()) {
impl.timers.erase(pos);
}
}
void fulfill() {
fulfiller.fulfill();
impl.timers.erase(pos);
pos = impl.timers.end();
}
const TimePoint time;
private:
PromiseFulfiller<void>& fulfiller;
TimerImpl::Impl& impl;
Impl::Timers::const_iterator pos;
};
inline bool TimerImpl::Impl::TimerBefore::operator()(
TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs) {
return lhs->time < rhs->time;
}
Promise<void> TimerImpl::atTime(TimePoint time) {
return newAdaptedPromise<void, TimerPromiseAdapter>(*impl, time);
}
Promise<void> TimerImpl::afterDelay(Duration delay) {
return newAdaptedPromise<void, TimerPromiseAdapter>(*impl, time + delay);
}
TimerImpl::TimerImpl(TimePoint startTime)
: time(startTime), impl(heap<Impl>()) {}
TimerImpl::~TimerImpl() noexcept(false) {}
Maybe<TimePoint> TimerImpl::nextEvent() {
auto iter = impl->timers.begin();
if (iter == impl->timers.end()) {
return nullptr;
} else {
return (*iter)->time;
}
}
Maybe<uint64_t> TimerImpl::timeoutToNextEvent(TimePoint start, Duration unit, uint64_t max) {
return nextEvent().map([&](TimePoint nextTime) -> uint64_t {
if (nextTime <= start) return 0;
Duration timeout = nextTime - start;
uint64_t result = timeout / unit;
bool roundUp = timeout % unit > 0 * SECONDS;
if (result >= max) {
return max;
} else {
return result + roundUp;
}
});
}
void TimerImpl::advanceTo(TimePoint newTime) {
KJ_REQUIRE(newTime >= time, "can't advance backwards in time") { return; }
time = newTime;
for (;;) {
auto front = impl->timers.begin();
if (front == impl->timers.end() || (*front)->time > time) {
break;
}
(*front)->fulfill();
}
}
} // namespace kj
......@@ -27,7 +27,6 @@
#pragma GCC system_header
#endif
#include "async.h"
#include "units.h"
#include <inttypes.h>
......@@ -71,104 +70,6 @@ Clock& nullClock();
// A clock which always returns UNIX_EPOCH as the current time. Useful when you don't care about
// time.
class Timer {
// Interface to time and timer functionality.
//
// Each `Timer` may have a different origin, and some `Timer`s may in fact tick at a different
// rate than real time (e.g. a `Timer` could represent CPU time consumed by a thread). However,
// all `Timer`s are monotonic: time will never appear to move backwards, even if the calendar
// date as tracked by the system is manually modified.
public:
virtual TimePoint now() = 0;
// Returns the current value of a clock that moves steadily forward, independent of any
// changes in the wall clock. The value is updated every time the event loop waits,
// and is constant in-between waits.
virtual Promise<void> atTime(TimePoint time) = 0;
// Returns a promise that returns as soon as now() >= time.
virtual Promise<void> afterDelay(Duration delay) = 0;
// Equivalent to atTime(now() + delay).
template <typename T>
Promise<T> timeoutAt(TimePoint time, Promise<T>&& promise) KJ_WARN_UNUSED_RESULT;
// Return a promise equivalent to `promise` but which throws an exception (and cancels the
// original promise) if it hasn't completed by `time`. The thrown exception is of type
// "OVERLOADED".
template <typename T>
Promise<T> timeoutAfter(Duration delay, Promise<T>&& promise) KJ_WARN_UNUSED_RESULT;
// Return a promise equivalent to `promise` but which throws an exception (and cancels the
// original promise) if it hasn't completed after `delay` from now. The thrown exception is of
// type "OVERLOADED".
private:
static kj::Exception makeTimeoutException();
};
class TimerImpl final: public Timer {
// Implementation of Timer that expects an external caller -- usually, the EventPort
// implementation -- to tell it when time has advanced.
public:
TimerImpl(TimePoint startTime);
~TimerImpl() noexcept(false);
Maybe<TimePoint> nextEvent();
// Returns the time at which the next scheduled timer event will occur, or null if no timer
// events are scheduled.
Maybe<uint64_t> timeoutToNextEvent(TimePoint start, Duration unit, uint64_t max);
// Convenience method which computes a timeout value to pass to an event-waiting system call to
// cause it to time out when the next timer event occurs.
//
// `start` is the time at which the timeout starts counting. This is typically not the same as
// now() since some time may have passed since the last time advanceTo() was called.
//
// `unit` is the time unit in which the timeout is measured. This is often MILLISECONDS. Note
// that this method will fractional values *up*, to guarantee that the returned timeout waits
// until just *after* the time the event is scheduled.
//
// The timeout will be clamped to `max`. Use this to avoid an overflow if e.g. the OS wants a
// 32-bit value or a signed value.
//
// Returns nullptr if there are no future events.
void advanceTo(TimePoint newTime);
// Set the time to `time` and fire any at() events that have been passed.
// implements Timer ----------------------------------------------------------
TimePoint now() override;
Promise<void> atTime(TimePoint time) override;
Promise<void> afterDelay(Duration delay) override;
private:
struct Impl;
class TimerPromiseAdapter;
TimePoint time;
Own<Impl> impl;
};
// =======================================================================================
// inline implementation details
template <typename T>
Promise<T> Timer::timeoutAt(TimePoint time, Promise<T>&& promise) {
return promise.exclusiveJoin(atTime(time).then([]() -> kj::Promise<T> {
return makeTimeoutException();
}));
}
template <typename T>
Promise<T> Timer::timeoutAfter(Duration delay, Promise<T>&& promise) {
return promise.exclusiveJoin(afterDelay(delay).then([]() -> kj::Promise<T> {
return makeTimeoutException();
}));
}
inline TimePoint TimerImpl::now() { return time; }
} // namespace kj
#endif // KJ_TIME_H_
// Copyright (c) 2014 Google Inc. (contributed by Remy Blank <rblank@google.com>)
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "timer.h"
#include "debug.h"
#include <set>
namespace kj {
kj::Exception Timer::makeTimeoutException() {
return KJ_EXCEPTION(OVERLOADED, "operation timed out");
}
struct TimerImpl::Impl {
struct TimerBefore {
bool operator()(TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs);
};
using Timers = std::multiset<TimerPromiseAdapter*, TimerBefore>;
Timers timers;
};
class TimerImpl::TimerPromiseAdapter {
public:
TimerPromiseAdapter(PromiseFulfiller<void>& fulfiller, TimerImpl::Impl& impl, TimePoint time)
: time(time), fulfiller(fulfiller), impl(impl) {
pos = impl.timers.insert(this);
}
~TimerPromiseAdapter() {
if (pos != impl.timers.end()) {
impl.timers.erase(pos);
}
}
void fulfill() {
fulfiller.fulfill();
impl.timers.erase(pos);
pos = impl.timers.end();
}
const TimePoint time;
private:
PromiseFulfiller<void>& fulfiller;
TimerImpl::Impl& impl;
Impl::Timers::const_iterator pos;
};
inline bool TimerImpl::Impl::TimerBefore::operator()(
TimerPromiseAdapter* lhs, TimerPromiseAdapter* rhs) {
return lhs->time < rhs->time;
}
Promise<void> TimerImpl::atTime(TimePoint time) {
return newAdaptedPromise<void, TimerPromiseAdapter>(*impl, time);
}
Promise<void> TimerImpl::afterDelay(Duration delay) {
return newAdaptedPromise<void, TimerPromiseAdapter>(*impl, time + delay);
}
TimerImpl::TimerImpl(TimePoint startTime)
: time(startTime), impl(heap<Impl>()) {}
TimerImpl::~TimerImpl() noexcept(false) {}
Maybe<TimePoint> TimerImpl::nextEvent() {
auto iter = impl->timers.begin();
if (iter == impl->timers.end()) {
return nullptr;
} else {
return (*iter)->time;
}
}
Maybe<uint64_t> TimerImpl::timeoutToNextEvent(TimePoint start, Duration unit, uint64_t max) {
return nextEvent().map([&](TimePoint nextTime) -> uint64_t {
if (nextTime <= start) return 0;
Duration timeout = nextTime - start;
uint64_t result = timeout / unit;
bool roundUp = timeout % unit > 0 * SECONDS;
if (result >= max) {
return max;
} else {
return result + roundUp;
}
});
}
void TimerImpl::advanceTo(TimePoint newTime) {
KJ_REQUIRE(newTime >= time, "can't advance backwards in time") { return; }
time = newTime;
for (;;) {
auto front = impl->timers.begin();
if (front == impl->timers.end() || (*front)->time > time) {
break;
}
(*front)->fulfill();
}
}
} // namespace kj
// Copyright (c) 2014 Google Inc. (contributed by Remy Blank <rblank@google.com>)
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef KJ_TIMER_H_
#define KJ_TIMER_H_
#if defined(__GNUC__) && !KJ_HEADER_WARNINGS
#pragma GCC system_header
#endif
#include "time.h"
#include "async.h"
namespace kj {
class Timer {
// Interface to time and timer functionality.
//
// Each `Timer` may have a different origin, and some `Timer`s may in fact tick at a different
// rate than real time (e.g. a `Timer` could represent CPU time consumed by a thread). However,
// all `Timer`s are monotonic: time will never appear to move backwards, even if the calendar
// date as tracked by the system is manually modified.
public:
virtual TimePoint now() = 0;
// Returns the current value of a clock that moves steadily forward, independent of any
// changes in the wall clock. The value is updated every time the event loop waits,
// and is constant in-between waits.
virtual Promise<void> atTime(TimePoint time) = 0;
// Returns a promise that returns as soon as now() >= time.
virtual Promise<void> afterDelay(Duration delay) = 0;
// Equivalent to atTime(now() + delay).
template <typename T>
Promise<T> timeoutAt(TimePoint time, Promise<T>&& promise) KJ_WARN_UNUSED_RESULT;
// Return a promise equivalent to `promise` but which throws an exception (and cancels the
// original promise) if it hasn't completed by `time`. The thrown exception is of type
// "OVERLOADED".
template <typename T>
Promise<T> timeoutAfter(Duration delay, Promise<T>&& promise) KJ_WARN_UNUSED_RESULT;
// Return a promise equivalent to `promise` but which throws an exception (and cancels the
// original promise) if it hasn't completed after `delay` from now. The thrown exception is of
// type "OVERLOADED".
private:
static kj::Exception makeTimeoutException();
};
class TimerImpl final: public Timer {
// Implementation of Timer that expects an external caller -- usually, the EventPort
// implementation -- to tell it when time has advanced.
public:
TimerImpl(TimePoint startTime);
~TimerImpl() noexcept(false);
Maybe<TimePoint> nextEvent();
// Returns the time at which the next scheduled timer event will occur, or null if no timer
// events are scheduled.
Maybe<uint64_t> timeoutToNextEvent(TimePoint start, Duration unit, uint64_t max);
// Convenience method which computes a timeout value to pass to an event-waiting system call to
// cause it to time out when the next timer event occurs.
//
// `start` is the time at which the timeout starts counting. This is typically not the same as
// now() since some time may have passed since the last time advanceTo() was called.
//
// `unit` is the time unit in which the timeout is measured. This is often MILLISECONDS. Note
// that this method will fractional values *up*, to guarantee that the returned timeout waits
// until just *after* the time the event is scheduled.
//
// The timeout will be clamped to `max`. Use this to avoid an overflow if e.g. the OS wants a
// 32-bit value or a signed value.
//
// Returns nullptr if there are no future events.
void advanceTo(TimePoint newTime);
// Set the time to `time` and fire any at() events that have been passed.
// implements Timer ----------------------------------------------------------
TimePoint now() override;
Promise<void> atTime(TimePoint time) override;
Promise<void> afterDelay(Duration delay) override;
private:
struct Impl;
class TimerPromiseAdapter;
TimePoint time;
Own<Impl> impl;
};
// =======================================================================================
// inline implementation details
template <typename T>
Promise<T> Timer::timeoutAt(TimePoint time, Promise<T>&& promise) {
return promise.exclusiveJoin(atTime(time).then([]() -> kj::Promise<T> {
return makeTimeoutException();
}));
}
template <typename T>
Promise<T> Timer::timeoutAfter(Duration delay, Promise<T>&& promise) {
return promise.exclusiveJoin(afterDelay(delay).then([]() -> kj::Promise<T> {
return makeTimeoutException();
}));
}
inline TimePoint TimerImpl::now() { return time; }
} // namespace kj
#endif // KJ_TIMER_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment