blob: 3c71fd30c10e824df62df6af24797c6e5f29810c [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "stdafx.h"
#include <sys/stat.h>
#define MAX_URI_LENGTH 512
//TODO: x-plat definitions
#ifdef _WIN32
#define TTD_MAX_FILE_LENGTH MAX_PATH
#define TTD_HOST_PATH_SEP "\\"
#else
#define TTD_MAX_FILE_LENGTH MAX_URI_LENGTH
#define TTD_HOST_PATH_SEP "/"
#endif
void TTDHostBuildCurrentExeDirectory(char* path, size_t* pathLength, size_t bufferLength)
{
char exePath[TTD_MAX_FILE_LENGTH];
PlatformAgnostic::SystemInfo::GetBinaryLocation(exePath, TTD_MAX_FILE_LENGTH);
size_t i = strlen(exePath) - 1;
while (exePath[i] != TTD_HOST_PATH_SEP[0] && i != 0)
{
--i;
}
if (i == 0)
{
fwprintf(stderr, _u("Can't get current exe directory"));
exit(1);
}
if (i + 2 > bufferLength)
{
fwprintf(stderr, _u("Don't overflow path buffer during copy"));
exit(1);
}
memcpy_s(path, bufferLength, exePath, i + 1);
*pathLength = i + 1;
path[*pathLength] = '\0';
}
#ifdef _WIN32
int TTDHostMKDir(const char* path, size_t pathLength)
{
char16 cpath[MAX_PATH];
LPCUTF8 pathbase = (LPCUTF8)path;
if(MAX_PATH <= pathLength) //<= to account for null terminator
{
wprintf(_u("Don't overflow path buffer during conversion"));
exit(1);
}
utf8::DecodeUnitsIntoAndNullTerminate(cpath, pathbase, pathbase + pathLength);
return _wmkdir(cpath);
}
JsTTDStreamHandle TTDHostOpen(size_t pathLength, const char* path, bool isWrite)
{
char16 wpath[MAX_PATH];
LPCUTF8 pathbase = (LPCUTF8)path;
if(MAX_PATH <= pathLength) //<= to account for null terminator
{
wprintf(_u("Don't overflow path buffer during conversion"));
exit(1);
}
utf8::DecodeUnitsIntoAndNullTerminate(wpath, pathbase, pathbase + pathLength);
FILE* res = nullptr;
_wfopen_s(&res, wpath, isWrite ? _u("w+b") : _u("r+b"));
return (JsTTDStreamHandle)res;
}
#define TTDHostRead(buff, size, handle) fread_s(buff, size, 1, size, (FILE*)handle);
#define TTDHostWrite(buff, size, handle) fwrite(buff, 1, size, (FILE*)handle)
#else
int TTDHostMKDir(const char* path, size_t pathLength)
{
return mkdir(path, 0700);
}
JsTTDStreamHandle TTDHostOpen(size_t pathLength, const char* path, bool isWrite)
{
return (JsTTDStreamHandle)fopen(path, isWrite ? "w+b" : "r+b");
}
#define TTDHostRead(buff, size, handle) fread(buff, 1, size, (FILE*)handle)
#define TTDHostWrite(buff, size, handle) fwrite(buff, 1, size, (FILE*)handle)
#endif
int GetPathNameLocation(LPCSTR filename)
{
int filenameLength = (int) strlen(filename);
int pos;
if (filenameLength <= 0)
{
return -1;
}
for (pos = filenameLength - 1; pos >= 0; pos--)
{
char ch = filename[pos];
if (ch == '/' || ch == '\\') break;
}
return pos;
}
inline void pathcpy(char * target, LPCSTR src, uint length)
{
#ifndef _WIN32
for (int i = 0; i < length; i++)
{
if (src[i] == '\\')
{
target[i] = '/';
}
else
{
target[i] = src[i];
}
}
#else
memcpy(target, src, length);
#endif
}
uint ConcatPath(LPCSTR filenameLeft, uint posPathSep, LPCSTR filenameRight, char* buffer, uint bufferLength)
{
int filenameRightLength = (int) strlen(filenameRight);
// [ path[/] ] + [filename] + /0
uint totalLength = posPathSep + filenameRightLength + 1;
if (buffer == nullptr)
{
return totalLength;
}
if (bufferLength < totalLength)
{
fprintf(stderr, "Error: file path is too long.\n");
return (uint)-1;
}
pathcpy(buffer, filenameLeft, posPathSep);
buffer += posPathSep;
pathcpy(buffer, filenameRight, filenameRightLength);
buffer += filenameRightLength;
buffer[0] = char(0);
return totalLength;
}
HRESULT Helpers::LoadScriptFromFile(LPCSTR filenameToLoad, LPCSTR& contents, UINT* lengthBytesOut /*= nullptr*/, std::string* fullPath /*= nullptr*/, bool shouldMute /*=false */)
{
static char sHostApplicationPathBuffer[MAX_URI_LENGTH];
static uint sHostApplicationPathBufferLength = (uint) -1;
char combinedPathBuffer[MAX_URI_LENGTH];
HRESULT hr = S_OK;
BYTE * pRawBytes = nullptr;
BYTE * pRawBytesFromMap = nullptr;
UINT lengthBytes = 0;
contents = nullptr;
FILE * file = NULL;
size_t bufferLength = 0;
LPCSTR filename = fullPath == nullptr ? filenameToLoad : LPCSTR(fullPath->c_str());
if (sHostApplicationPathBufferLength == (uint)-1)
{
// consider incoming filename as the host app and base its' path for others
sHostApplicationPathBufferLength = GetPathNameLocation(filename);
if (sHostApplicationPathBufferLength == -1)
{
// host app has no path info. (it must be located on current folder!)
sHostApplicationPathBufferLength = 0;
}
else
{
sHostApplicationPathBufferLength += 1;
Assert(sHostApplicationPathBufferLength < MAX_URI_LENGTH);
// save host app's path and fix the path separator for platform
pathcpy(sHostApplicationPathBuffer, filename, sHostApplicationPathBufferLength);
}
sHostApplicationPathBuffer[sHostApplicationPathBufferLength] = char(0);
}
else if (filename[0] != '/' && filename[0] != '\\' && fullPath == nullptr) // make sure it's not a full path
{
// concat host path and filename
uint len = ConcatPath(sHostApplicationPathBuffer, sHostApplicationPathBufferLength,
filename, combinedPathBuffer, MAX_URI_LENGTH);
if (len == (uint)-1)
{
hr = E_FAIL;
goto Error;
}
filename = combinedPathBuffer;
}
// check if have it registered
AutoString *data;
if (SourceMap::Find(filenameToLoad, strlen(filenameToLoad), &data) ||
SourceMap::Find(filename, strlen(filename), &data))
{
pRawBytesFromMap = (BYTE*) data->GetString();
lengthBytes = (UINT) data->GetLength();
}
else
{
// Open the file as a binary file to prevent CRT from handling encoding, line-break conversions,
// etc.
if (fopen_s(&file, filename, "rb") != 0)
{
if (!HostConfigFlags::flags.MuteHostErrorMsgIsEnabled && !shouldMute)
{
#ifdef _WIN32
DWORD lastError = GetLastError();
char16 wszBuff[MAX_URI_LENGTH];
fprintf(stderr, "Error in opening file '%s' ", filename);
wszBuff[0] = 0;
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
lastError,
0,
wszBuff,
_countof(wszBuff),
nullptr))
{
fwprintf(stderr, _u(": %s"), wszBuff);
}
fwprintf(stderr, _u("\n"));
#elif defined(_POSIX_VERSION)
fprintf(stderr, "Error in opening file: ");
perror(filename);
#endif
}
IfFailGo(E_FAIL);
}
}
if (file != NULL)
{
// Determine the file length, in bytes.
fseek(file, 0, SEEK_END);
lengthBytes = ftell(file);
fseek(file, 0, SEEK_SET);
}
if (lengthBytes != 0)
{
bufferLength = lengthBytes + sizeof(BYTE);
pRawBytes = (LPBYTE)malloc(bufferLength);
}
else
{
bufferLength = 1;
pRawBytes = (LPBYTE)malloc(bufferLength);
}
if (nullptr == pRawBytes)
{
fwprintf(stderr, _u("out of memory"));
IfFailGo(E_OUTOFMEMORY);
}
if (lengthBytes != 0)
{
if (file != NULL)
{
//
// Read the entire content as a binary block.
//
size_t readBytes = fread(pRawBytes, sizeof(BYTE), lengthBytes, file);
if (readBytes < lengthBytes * sizeof(BYTE))
{
IfFailGo(E_FAIL);
}
}
else // from module source register
{
// Q: module source is on persistent memory. Why do we use the copy instead?
// A: if we use the same memory twice, ch doesn't know that during FinalizeCallback free.
// the copy memory will be freed by the finalizer
Assert(pRawBytesFromMap);
memcpy_s(pRawBytes, bufferLength, pRawBytesFromMap, lengthBytes);
}
}
if (pRawBytes)
{
pRawBytes[lengthBytes] = 0; // Null terminate it. Could be UTF16
}
if (file != NULL)
{
//
// Read encoding to make sure it's supported
//
// Warning: The UNICODE buffer for parsing is supposed to be provided by the host.
// This is not a complete read of the encoding. Some encodings like UTF7, UTF1, EBCDIC, SCSU, BOCU could be
// wrongly classified as ANSI
//
#pragma warning(push)
// suppressing prefast warning that "readable size is bufferLength
// bytes but 2 may be read" as bufferLength is clearly > 2 in the code that follows
#pragma warning(disable:6385)
C_ASSERT(sizeof(WCHAR) == 2);
if (bufferLength > 2)
{
__analysis_assume(bufferLength > 2);
#pragma prefast(push)
#pragma prefast(disable:6385, "PREfast incorrectly reports this as an out-of-bound access.");
if ((pRawBytes[0] == 0xFE && pRawBytes[1] == 0xFF) ||
(pRawBytes[0] == 0xFF && pRawBytes[1] == 0xFE) ||
(bufferLength > 4 && pRawBytes[0] == 0x00 && pRawBytes[1] == 0x00 &&
((pRawBytes[2] == 0xFE && pRawBytes[3] == 0xFF) ||
(pRawBytes[2] == 0xFF && pRawBytes[3] == 0xFE))))
{
// unicode unsupported
fwprintf(stderr, _u("unsupported file encoding. Only ANSI and UTF8 supported"));
IfFailGo(E_UNEXPECTED);
}
#pragma prefast(pop)
}
#pragma warning(pop)
}
contents = reinterpret_cast<LPCSTR>(pRawBytes);
Error:
if (SUCCEEDED(hr))
{
if (lengthBytesOut)
{
*lengthBytesOut = lengthBytes;
}
}
if (file != NULL)
{
fclose(file);
}
if (pRawBytes && reinterpret_cast<LPCSTR>(pRawBytes) != contents)
{
free(pRawBytes);
}
return hr;
}
LPCWSTR Helpers::JsErrorCodeToString(JsErrorCode jsErrorCode)
{
bool hasException = false;
ChakraRTInterface::JsHasException(&hasException);
if (hasException)
{
WScriptJsrt::PrintException("", JsErrorScriptException);
}
switch (jsErrorCode)
{
case JsNoError: return _u("JsNoError");
// JsErrorCategoryUsage
case JsErrorCategoryUsage: return _u("JsErrorCategoryUsage");
case JsErrorInvalidArgument: return _u("JsErrorInvalidArgument");
case JsErrorNullArgument: return _u("JsErrorNullArgument");
case JsErrorNoCurrentContext: return _u("JsErrorNoCurrentContext");
case JsErrorInExceptionState: return _u("JsErrorInExceptionState");
case JsErrorNotImplemented: return _u("JsErrorNotImplemented");
case JsErrorWrongThread: return _u("JsErrorWrongThread");
case JsErrorRuntimeInUse: return _u("JsErrorRuntimeInUse");
case JsErrorBadSerializedScript: return _u("JsErrorBadSerializedScript");
case JsErrorInDisabledState: return _u("JsErrorInDisabledState");
case JsErrorCannotDisableExecution: return _u("JsErrorCannotDisableExecution");
case JsErrorHeapEnumInProgress: return _u("JsErrorHeapEnumInProgress");
case JsErrorArgumentNotObject: return _u("JsErrorArgumentNotObject");
case JsErrorInProfileCallback: return _u("JsErrorInProfileCallback");
case JsErrorInThreadServiceCallback: return _u("JsErrorInThreadServiceCallback");
case JsErrorCannotSerializeDebugScript: return _u("JsErrorCannotSerializeDebugScript");
case JsErrorAlreadyDebuggingContext: return _u("JsErrorAlreadyDebuggingContext");
case JsErrorAlreadyProfilingContext: return _u("JsErrorAlreadyProfilingContext");
case JsErrorIdleNotEnabled: return _u("JsErrorIdleNotEnabled");
case JsCannotSetProjectionEnqueueCallback: return _u("JsCannotSetProjectionEnqueueCallback");
case JsErrorCannotStartProjection: return _u("JsErrorCannotStartProjection");
case JsErrorInObjectBeforeCollectCallback: return _u("JsErrorInObjectBeforeCollectCallback");
case JsErrorObjectNotInspectable: return _u("JsErrorObjectNotInspectable");
case JsErrorPropertyNotSymbol: return _u("JsErrorPropertyNotSymbol");
case JsErrorPropertyNotString: return _u("JsErrorPropertyNotString");
case JsErrorInvalidContext: return _u("JsErrorInvalidContext");
case JsInvalidModuleHostInfoKind: return _u("JsInvalidModuleHostInfoKind");
case JsErrorModuleParsed: return _u("JsErrorModuleParsed");
// JsErrorCategoryEngine
case JsErrorCategoryEngine: return _u("JsErrorCategoryEngine");
case JsErrorOutOfMemory: return _u("JsErrorOutOfMemory");
case JsErrorBadFPUState: return _u("JsErrorBadFPUState");
// JsErrorCategoryScript
case JsErrorCategoryScript: return _u("JsErrorCategoryScript");
case JsErrorScriptException: return _u("JsErrorScriptException");
case JsErrorScriptCompile: return _u("JsErrorScriptCompile");
case JsErrorScriptTerminated: return _u("JsErrorScriptTerminated");
case JsErrorScriptEvalDisabled: return _u("JsErrorScriptEvalDisabled");
// JsErrorCategoryFatal
case JsErrorCategoryFatal: return _u("JsErrorCategoryFatal");
case JsErrorFatal: return _u("JsErrorFatal");
case JsErrorWrongRuntime: return _u("JsErrorWrongRuntime");
// JsErrorCategoryDiagError
case JsErrorCategoryDiagError: return _u("JsErrorCategoryDiagError");
case JsErrorDiagAlreadyInDebugMode: return _u("JsErrorDiagAlreadyInDebugMode");
case JsErrorDiagNotInDebugMode: return _u("JsErrorDiagNotInDebugMode");
case JsErrorDiagNotAtBreak: return _u("JsErrorDiagNotAtBreak");
case JsErrorDiagInvalidHandle: return _u("JsErrorDiagInvalidHandle");
case JsErrorDiagObjectNotFound: return _u("JsErrorDiagObjectNotFound");
case JsErrorDiagUnableToPerformAction: return _u("JsErrorDiagUnableToPerformAction");
default:
return _u("<unknown>");
break;
}
}
void Helpers::LogError(__in __nullterminated const char16 *msg, ...)
{
va_list args;
va_start(args, msg);
wprintf(_u("ERROR: "));
vfwprintf(stderr, msg, args);
wprintf(_u("\n"));
fflush(stdout);
va_end(args);
}
HRESULT Helpers::LoadBinaryFile(LPCSTR filename, LPCSTR& contents, UINT& lengthBytes, bool printFileOpenError)
{
HRESULT hr = S_OK;
contents = nullptr;
lengthBytes = 0;
size_t result;
FILE * file;
//
// Open the file as a binary file to prevent CRT from handling encoding, line-break conversions,
// etc.
//
if (fopen_s(&file, filename, "rb") != 0)
{
if (printFileOpenError)
{
fprintf(stderr, "Error in opening file '%s' ", filename);
#ifdef _WIN32
DWORD lastError = GetLastError();
char16 wszBuff[MAX_URI_LENGTH];
wszBuff[0] = 0;
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
lastError,
0,
wszBuff,
_countof(wszBuff),
nullptr))
{
fwprintf(stderr, _u(": %s"), wszBuff);
}
#endif
fprintf(stderr, "\n");
}
return E_FAIL;
}
// file will not be nullptr if _wfopen_s succeeds
__analysis_assume(file != nullptr);
//
// Determine the file length, in bytes.
//
fseek(file, 0, SEEK_END);
lengthBytes = ftell(file);
fseek(file, 0, SEEK_SET);
contents = (LPCSTR)HeapAlloc(GetProcessHeap(), 0, lengthBytes);
if (nullptr == contents)
{
fwprintf(stderr, _u("out of memory"));
IfFailGo(E_OUTOFMEMORY);
}
//
// Read the entire content as a binary block.
//
result = fread((void*)contents, sizeof(char), lengthBytes, file);
if (result != lengthBytes)
{
fwprintf(stderr, _u("Read error"));
IfFailGo(E_FAIL);
}
Error:
fclose(file);
if (contents && FAILED(hr))
{
HeapFree(GetProcessHeap(), 0, (void*)contents);
contents = nullptr;
}
return hr;
}
void Helpers::TTReportLastIOErrorAsNeeded(BOOL ok, const char* msg)
{
if(!ok)
{
#ifdef _WIN32
DWORD lastError = GetLastError();
LPTSTR pTemp = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lastError, 0, (LPTSTR)&pTemp, 0, NULL);
fwprintf(stderr, _u("Error is: %s\n"), pTemp);
LocalFree(pTemp);
#else
fprintf(stderr, "Error is: %i %s\n", errno, strerror(errno));
#endif
fprintf(stderr, "Message is: %s\n", msg);
AssertMsg(false, "IO Error!!!");
exit(1);
}
}
//We assume bounded ascii path length for simplicity
#define MAX_TTD_ASCII_PATH_EXT_LENGTH 64
void Helpers::CreateTTDDirectoryAsNeeded(size_t* uriLength, char* uri, const char* asciiDir1, const wchar* asciiDir2)
{
if(*uriLength + strlen(asciiDir1) + wcslen(asciiDir2) + 2 > MAX_URI_LENGTH || strlen(asciiDir1) >= MAX_TTD_ASCII_PATH_EXT_LENGTH || wcslen(asciiDir2) >= MAX_TTD_ASCII_PATH_EXT_LENGTH)
{
wprintf(_u("We assume bounded MAX_URI_LENGTH for simplicity.\n"));
wprintf(_u("%S, %S, %ls\n"), uri, asciiDir1, asciiDir2);
exit(1);
}
int success = 0;
int extLength = 0;
extLength = sprintf_s(uri + *uriLength, MAX_TTD_ASCII_PATH_EXT_LENGTH, "%s%s", asciiDir1, TTD_HOST_PATH_SEP);
if(extLength == -1 || MAX_URI_LENGTH < (*uriLength) + extLength)
{
wprintf(_u("Failed directory extension 1.\n"));
wprintf(_u("%S, %S, %ls\n"), uri, asciiDir1, asciiDir2);
exit(1);
}
*uriLength += extLength;
success = TTDHostMKDir(uri, *uriLength);
if(success != 0)
{
//we may fail because someone else created the directory -- that is ok
Helpers::TTReportLastIOErrorAsNeeded(errno == EEXIST, "Failed to create directory");
}
char realAsciiDir2[MAX_TTD_ASCII_PATH_EXT_LENGTH];
size_t asciiDir2Length = wcslen(asciiDir2) + 1;
for(size_t i = 0; i < asciiDir2Length; ++i)
{
if(asciiDir2[i] > CHAR_MAX)
{
wprintf(_u("Test directory names can only include ascii chars.\n"));
exit(1);
}
realAsciiDir2[i] = (char)asciiDir2[i];
}
extLength = sprintf_s(uri + *uriLength, MAX_TTD_ASCII_PATH_EXT_LENGTH, "%s%s", realAsciiDir2, TTD_HOST_PATH_SEP);
if(extLength == -1 || MAX_URI_LENGTH < *uriLength + extLength)
{
wprintf(_u("Failed directory create 2.\n"));
wprintf(_u("%S, %S, %ls\n"), uri, asciiDir1, asciiDir2);
exit(1);
}
*uriLength += extLength;
success = TTDHostMKDir(uri, *uriLength);
if(success != 0)
{
//we may fail because someone else created the directory -- that is ok
Helpers::TTReportLastIOErrorAsNeeded(errno == EEXIST, "Failed to create directory");
}
}
void Helpers::GetTTDDirectory(const wchar* curi, size_t* uriLength, char* uri, size_t bufferLength)
{
TTDHostBuildCurrentExeDirectory(uri, uriLength, bufferLength);
Helpers::CreateTTDDirectoryAsNeeded(uriLength, uri, "_ttdlog", curi);
}
JsTTDStreamHandle CALLBACK Helpers::TTCreateStreamCallback(size_t uriLength, const char* uri, size_t asciiNameLength, const char* asciiName, bool read, bool write)
{
AssertMsg((read | write) & (!read | !write), "Read/Write streams not supported yet -- defaulting to read only");
if(uriLength + asciiNameLength + 1 > MAX_URI_LENGTH)
{
wprintf(_u("We assume bounded MAX_URI_LENGTH for simplicity."));
exit(1);
}
char path[MAX_URI_LENGTH];
memset(path, 0, MAX_URI_LENGTH);
memcpy_s(path, MAX_URI_LENGTH, uri, uriLength);
memcpy_s(path + uriLength, MAX_URI_LENGTH - uriLength, asciiName, asciiNameLength);
JsTTDStreamHandle res = TTDHostOpen(uriLength + asciiNameLength, path, write);
if(res == nullptr)
{
fprintf(stderr, "Failed to open file: %s\n", path);
}
Helpers::TTReportLastIOErrorAsNeeded(res != nullptr, "Failed File Open");
return res;
}
bool CALLBACK Helpers::TTReadBytesFromStreamCallback(JsTTDStreamHandle handle, byte* buff, size_t size, size_t* readCount)
{
AssertMsg(handle != nullptr, "Bad file handle.");
if(size > MAXDWORD)
{
*readCount = 0;
return false;
}
BOOL ok = FALSE;
*readCount = TTDHostRead(buff, size, (FILE*)handle);
ok = (*readCount != 0);
Helpers::TTReportLastIOErrorAsNeeded(ok, "Failed Read!!!");
return ok ? true : false;
}
bool CALLBACK Helpers::TTWriteBytesToStreamCallback(JsTTDStreamHandle handle, const byte* buff, size_t size, size_t* writtenCount)
{
AssertMsg(handle != nullptr, "Bad file handle.");
if(size > MAXDWORD)
{
*writtenCount = 0;
return false;
}
BOOL ok = FALSE;
*writtenCount = TTDHostWrite(buff, size, (FILE*)handle);
ok = (*writtenCount == size);
Helpers::TTReportLastIOErrorAsNeeded(ok, "Failed Read!!!");
return ok ? true : false;
}
void CALLBACK Helpers::TTFlushAndCloseStreamCallback(JsTTDStreamHandle handle, bool read, bool write)
{
fflush((FILE*)handle);
fclose((FILE*)handle);
}
void GetBinaryPathWithFileNameA(char *path, const size_t buffer_size, const char* filename)
{
char fullpath[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char modulename[_MAX_PATH];
PlatformAgnostic::SystemInfo::GetBinaryLocation(modulename, _MAX_PATH);
_splitpath_s(modulename, drive, _MAX_DRIVE, dir, _MAX_DIR, nullptr, 0, nullptr, 0);
_makepath_s(fullpath, drive, dir, filename, nullptr);
size_t len = strlen(fullpath);
if (len < buffer_size)
{
memcpy(path, fullpath, len * sizeof(char));
}
else
{
len = 0;
}
path[len] = char(0);
}