#include "cache.hpp" // Use srcdata and buildOpts to generate a cache entry // // In: // isCacheReady - Indicate whether cache file strucutre is set up // srcData - Source data // buildOpts - Build options // dstData - Destination data // // Out: // none // // Returns: // true if entry created; false otherwise, check errorMsg for errors // bool KernelCache::makeCacheEntry(bool isCacheReady, const KernelCacheData *srcData, const std::string &buildOpts, const std::string &dstData) { if (!isCacheReady) { errorMsg = "makeCacheEntry() failed because cache file structure is not set up successfully"; appendLogToFile(); return false; } errorMsg.clear(); std::string fileName; makeFileName(srcData, buildOpts, fileName); /* Write all info to cache file */ const size_t buildOptsSize = buildOpts.size(); const size_t dstDataSize = dstData.size(); #if _WIN32 HANDLE cacheFile = CreateFile(fileName.c_str(), GENERIC_WRITE | WRITE_OWNER | READ_CONTROL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (cacheFile == INVALID_HANDLE_VALUE) { errorMsg = "Error opening file for writing: " + getLastErrorMsg(); return false; } #else int cacheFile = open(fileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (cacheFile == -1) { errorMsg = "Error opening file for writing: " + getLastErrorMsg(); return false; } // Exclusive write lock for cache file struct flock fl = {F_WRLCK, SEEK_SET, 0, 0}; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error setting file write lock: " + getLastErrorMsg(), cacheFile); return false; } #endif // Write kernel cache file header, build options, source data size, source data // and destination data to cache file KernelCacheFileHeader H = { {'A', 'M', 'D', '\0'}, buildOptsSize, dstDataSize }; if (!writeFile(cacheFile, &H, sizeof(KernelCacheFileHeader)) || !writeFile(cacheFile, buildOpts.c_str(), buildOptsSize) || !writeFile(cacheFile, &(srcData->dataSize), sizeof(srcData->dataSize)) || !writeFile(cacheFile, srcData->data, srcData->dataSize) || !writeFile(cacheFile, dstData.c_str(), dstDataSize)) { removePartiallyWrittenFile(fileName); return false; } #if __linux__ // Unlock the file fl.l_type = F_UNLCK; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error unlock file write lock: " + getLastErrorMsg(), cacheFile); return false; } #endif CloseFile(cacheFile); // Set file to only owner accessible if (!setAccessPermission(fileName, true)) { return false; } // Update cache info unsigned int cacheFileSize = sizeof(KernelCacheFileHeader) + buildOptsSize + sizeof(srcData->dataSize) + srcData->dataSize + dstDataSize; if (!setCacheInfo(version, cacheSize + cacheFileSize)) { errorMsg = "Cache version and size is not updated successfully"; return false; } return true; } // Use srcData and buildOpts to find the corresponding cache entry, if it exists // // In: // deviceName - chip name // opts - Options object // srcData - Source data // buildOpts - Build options // Msg - message that need to passed for internal cache testing // // Out: // isCacheReady - Indicate whether cache file strucutre is set up // dstData - Destination data // // Returns: // true if entry found; false otherwise, check errorMsg for errors // bool KernelCache::getCacheEntry(bool &isCacheReady, const std::string &deviceName, amd::option::Options *opts, const KernelCacheData *srcData, const std::string &buildOpts, std::string &dstData, const std::string &msg) { dstData.clear(); errorMsg.clear(); bool kernelCached = false; if (!opts->oVariables->DisableKernelCaching && opts->oVariables->OptLevel > 0) { isCacheReady = cacheInit(SC_BUILD_NUMBER, deviceName); if (!isCacheReady) { appendLogToFile(); } else { kernelCached = getCacheEntry_helper(srcData, buildOpts, dstData); // For internal kernel cache test only if (internalKCacheTestSwitch()) { std::string cacheMsg = msg; if (kernelCached) { cacheMsg += " is cached!\n"; } else { cacheMsg += " is not cached!\n"; } fprintf(stdout, cacheMsg.c_str()); fflush(stdout); } } } if (!errorMsg.empty()) { appendLogToFile(); } return kernelCached; } // Use srcData and buildOpts to find the corresponding cache entry, if it exists // // In: // srcData - Source data // buildOpts - Build options // // Out: // dstData - Destination data // // Returns: // true if entry found; false otherwise, check errorMsg for errors // bool KernelCache::getCacheEntry_helper(const KernelCacheData *srcData, const std::string &buildOpts, std::string &dstData) { std::string fileName; makeFileName(srcData, buildOpts, fileName); #if _WIN32 HANDLE cacheFile = CreateFile(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (cacheFile == INVALID_HANDLE_VALUE) { return false; } #else int cacheFile = open(fileName.c_str(), O_RDONLY); if (cacheFile == -1) { return false; } // Read lock for cache file struct flock fl = {F_RDLCK, SEEK_SET, 0, 0}; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error setting file read lock: " + getLastErrorMsg(), cacheFile); return false; } #endif // Read kernel cache file header KernelCacheFileHeader H; if (!readFile(cacheFile, &H, sizeof(KernelCacheFileHeader))) { return false; } // Compare kernel cache file header if (!verifyKernelCacheFileHeader(H, buildOpts)) { CloseFile(cacheFile); return false; } // Read build options char *fileBuildOpts = (char *)alloca(H.buildOptSize); if (!readFile(cacheFile, fileBuildOpts, H.buildOptSize)) { return false; } // Compare build options if (buildOpts.compare(0, H.buildOptSize, fileBuildOpts, H.buildOptSize)) { logErrorCloseFile("Cache collision: Build options do not match", cacheFile); return false; } // Get source data size size_t fileSrcDataSize = 0; if (!readFile(cacheFile, &fileSrcDataSize, sizeof(size_t))) { return false; } // Compare source data size if (fileSrcDataSize != srcData->dataSize) { logErrorCloseFile("Cache collision: Data size does not match", cacheFile); return false; } // Get source data std::unique_ptr fileSrcData(new char [fileSrcDataSize]); if (!fileSrcData) { logErrorCloseFile("Out of memory: " + getLastErrorMsg(), cacheFile); return false; } if (!readFile(cacheFile, fileSrcData.get(), fileSrcDataSize)) { return false; } // Compare source data if (memcmp(fileSrcData.get(), srcData->data, fileSrcDataSize)) { logErrorCloseFile("Cache collision: Size matches, contents do not", cacheFile); return false; } // Get cached content std::unique_ptr data(new char [H.dstSize]); if (!data) { logErrorCloseFile("Out of memory: " + getLastErrorMsg(), cacheFile); return false; } if (!readFile(cacheFile, data.get(), H.dstSize)) { return false; } dstData.assign(data.get(), H.dstSize); #if __linux__ // Unlock the file fl.l_type = F_UNLCK; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error unlock file read lock: " + getLastErrorMsg(), cacheFile); return false; } #endif CloseFile(cacheFile); return true; } #if _WIN32 // Get Sid of account // // In: // userName - accont name // // Out: // none // // Return: // Sid of account if SID is obtained; NULL otherwise // std::unique_ptr KernelCache::getSid(TCHAR *username) { if (username == NULL) { errorMsg = "Invalid user name in getSid mehtod"; return NULL; } // If a buffer is too small, the count parameter will be set to the size needed. const DWORD initialSize = 32; SID_NAME_USE sidNameUse; DWORD cbSid = initialSize, cchDomainName = initialSize; // Create buffers for the SID and the domain name std::unique_ptr sid = std::unique_ptr((SID*) new BYTE[initialSize]); if (!sid) { errorMsg = "Failed to allocate space for SID: " + getLastErrorMsg(); return NULL; } std::unique_ptr wszDomainName(new TCHAR[initialSize]); if (!wszDomainName) { errorMsg = "Failed to allocate space for domain name: " + getLastErrorMsg(); return NULL; } // Obtain the SID for the account name passed if (LookupAccountName(NULL, username, sid.get(), &cbSid, wszDomainName.get(), &cchDomainName, &sidNameUse)) { if (IsValidSid(sid.get()) == FALSE) { errorMsg = "The SID for the account is invalid: " + getLastErrorMsg(); return NULL; } return sid; } DWORD dwErrorCode = GetLastError(); if (dwErrorCode == ERROR_INSUFFICIENT_BUFFER) { if (cbSid > initialSize) { // Reallocate memory for the SID buffer sid = std::unique_ptr((SID*)new BYTE[cbSid]); if (!sid) { errorMsg = "Failed to allocate space for SID: " + getLastErrorMsg(); return NULL; } } if (cchDomainName > initialSize) { // Reallocate memory for the domain name buffer wszDomainName = std::unique_ptr(new TCHAR[cchDomainName]); if (!wszDomainName) { errorMsg = "Failed to allocate space for domain name: " + getLastErrorMsg(); return NULL; } } // Obtain the SID for the account name passed again if (LookupAccountName(NULL, username, sid.get(), &cbSid, wszDomainName.get(), &cchDomainName, &sidNameUse)) { if (IsValidSid(sid.get()) == FALSE) { errorMsg = "The SID for the account is invalid: " + getLastErrorMsg(); return NULL; } return sid; } } else { errorMsg = "Failed to get user security identifier for the account: " + getLastErrorMsg(); return NULL; } return sid; } #endif #if defined(__linux__) inline bool path_is_directory(const std::string &path) { struct stat s_buf; if (stat(path.c_str(), &s_buf)) return false; return S_ISDIR(s_buf.st_mode); } // Remove all files and subfolders in a dir // // In: // directory_name - folder name // // Out: // none // // Returns: // The number of files that are removed // static unsigned long fileCnt = 0; unsigned long remove_all(const char* directory_name) { DIR *dp; struct dirent *ep; char p_buf[PATH_MAX] = {0}; dp = opendir(directory_name); while ((ep = readdir(dp)) != NULL) { if (strcmp(ep->d_name, "..") && strcmp(ep->d_name, ".")) { snprintf(p_buf, PATH_MAX, "%s/%s", directory_name, ep->d_name); if (path_is_directory(p_buf)) { if (remove_all(p_buf) < 0) { return LONG_MIN; } } else { if (unlink(p_buf) != 0) { return LONG_MIN; } else { fileCnt++; } } } } closedir(dp); return (rmdir(directory_name) == 0) ? fileCnt : LONG_MIN; } #endif // Wipe the cache folder structure // // In: // none // // Out: // none // // Returns: // true if folder wipe is ok; false otherwise // bool KernelCache::wipeCacheFolders() { for (int i = 0; i < 16; ++i) { std::string dir = rootPath; std::stringstream ss; ss << amd::Os::fileSeparator() << std::hex << i; dir += ss.str(); if (amd::Os::pathExists(dir)) { #if _WIN32 std::tr2::sys::path mDir(dir); if (remove_all(mDir) < 0) { #else if (remove_all(dir.c_str()) < 0) { #endif errorMsg = "Error deleting cache directory"; return false; } } } return true; } // Setup cache tree structure // // In: // none // // Out: // none // // Returns: // true if folders setup is ok; false otherwise // bool KernelCache::setUpCacheFolders() { // Directory structure is distributed as 16 * 16 in order to keep the file count per directory low for (int i = 0; i < 16; ++i) { for (int j = 0; j < 16; ++j) { std::string dir = rootPath; std::stringstream ss; ss << amd::Os::fileSeparator() << std::hex << i << amd::Os::fileSeparator() << j; dir += ss.str(); if (false == amd::Os::createPath(dir)) { errorMsg = "Error creating directory in cache"; return false; } // Set folder to only owner accessible if (!setAccessPermission(rootPath)) { return false; } } } return true; } // Return detailed error message as string // // In: // None // // Out: // None // // Return: // Error message in string format. Otherwise, an empty string if there is no error // std::string KernelCache::getLastErrorMsg() { #if _WIN32 // Get the error message, if any. DWORD errorMessageID = GetLastError(); if (errorMessageID == 0) { return std::string(); //No error message has been recorded } LPSTR messageBuffer = nullptr; size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); std::string message(messageBuffer, size); LocalFree(messageBuffer); return message; #else return std::string(strerror(errno)); #endif } // Set file to only owner accessible // // In: // fileName - Path to file // isFile - True if fileName is a file, false if it is a path; false by default // // Out: // none // // Returns: // true if access permission is under control; false otherwise // bool KernelCache::setAccessPermission(const std::string &fileName, bool isFile) { #if _WIN32 TCHAR username[UNLEN + 1]; DWORD username_len = UNLEN + 1; if (!GetUserName(username, &username_len)) { errorMsg = "Failed to get user name for the account: " + getLastErrorMsg(); return false; } std::unique_ptr sid = getSid(username); if (!sid) { return false; } if (SetNamedSecurityInfo((LPTSTR)(fileName.c_str()), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, sid.get(), NULL, NULL, NULL) != ERROR_SUCCESS ) { errorMsg = "Failed to set user access permission: " + getLastErrorMsg(); return false; } #else if (!isFile) { int ret = chmod(fileName.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); if (ret < 0) { errorMsg = "Failed to set user access permission: " + getLastErrorMsg(); return false; } } #endif return true; } // Set the cache's root path // // In: // chipName - Chip name // // Out: // none // // Returns: // true if root path of cache is set successfully; false otherwise // bool KernelCache::setRootPath(const std::string &chipName) { rootPath.clear(); #if _WIN32 // Set root path to \AppData\Local\AMD\CLCache TCHAR userLocalAppDir[_MAX_PATH]; // Get path for user specific and non-roaming data if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, userLocalAppDir))) { rootPath = userLocalAppDir; } else { errorMsg = "User's local app dir is not found: " + getLastErrorMsg(); return false; } rootPath += "\\AMD\\CLCache"; #else // Set root path to /.AMD/CLCache struct passwd *pwd = getpwuid(getuid()); if (pwd == NULL) { errorMsg = getLastErrorMsg(); return false; } const char *homedir = pwd->pw_dir; if (homedir == NULL) { errorMsg = "Failed to get HOME directory: " + getLastErrorMsg(); return false; } rootPath = homedir; // Verify the path exists if (!amd::Os::pathExists(rootPath)) { errorMsg = "User's home directory is not created: " + getLastErrorMsg(); return false; } rootPath += "/.AMD/CLCache"; #endif rootPath += amd::Os::fileSeparator() + chipName; if (!amd::Os::createPath(rootPath)) { errorMsg = "Failed to create cache root directory"; return false; } // Set folder to only owner accessible return setAccessPermission(rootPath); } // Set the cache version and size // // In: // newVersion - New version for the cache // newSize - New size for the cache // // Out: // none // // Returns: // true if successful; false otherwise // bool KernelCache::setCacheInfo(unsigned int newVersion, unsigned int newSize) { unsigned int fileData[2]; fileData[0] = newVersion; fileData[1] = newSize; if (!writeFile(indexName, fileData, sizeof(fileData), false)) { removePartiallyWrittenFile(indexName); return false; } version = newVersion; cacheSize = newSize; return true; } // Get the version and size of the cache // // In: // none // // Out: // none // // Returns: // true if successful; false otherwise // bool KernelCache::getCacheInfo() { indexName = rootPath; indexName += amd::Os::fileSeparator(); indexName += "cacheDir"; #if _WIN32 HANDLE cacheFile = CreateFile(indexName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (cacheFile == INVALID_HANDLE_VALUE) { return setCacheInfo(-1, 0); } #else int cacheFile = open(indexName.c_str(), O_RDONLY); if (cacheFile == -1) { return setCacheInfo(-1, 0); } // Read lock for cache file struct flock fl = {F_RDLCK, SEEK_SET, 0, 0}; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error setting file read lock: " + getLastErrorMsg(), cacheFile); return false; } #endif if (!readFile(cacheFile, &version, sizeof(unsigned int))) { return false; } if (!readFile(cacheFile, &cacheSize, sizeof(unsigned int))) { return false; } #if __linux__ // Unlock the file fl.l_type = F_UNLCK; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error unlock file read lock: " + getLastErrorMsg(), cacheFile); return false; } #endif CloseFile(cacheFile); return true; } // Initialize the cache // // In: // compilerVersion - Compiler version // // Out: // none // // Returns: // true if successful; false otherwise // bool KernelCache::cacheInit(unsigned int compilerVersion, const std::string &chipName) { if (!setRootPath(chipName)) { return false; } if (!getCacheInfo()) { return false; } // Limit cache size to default cache size, and wipe out all cache files when it's exceed // TODO: need to implement cache eviction policy if (version != compilerVersion || cacheSize > DEFAULT_CACHE_SIZE) { if (!wipeCacheFolders() || !setCacheInfo(compilerVersion, 0) || !setUpCacheFolders()) { return false; } } return true; } // Compute the hash value for a buffer of data along with the buildOpts // // In: // data - Data to hash // buildOpts - Build options // // Out: // none // // Returns: // Hash value computed from the inputs // unsigned int KernelCache::computeHash(const KernelCacheData *data, const std::string &buildOpts) { HashType v = { std::string(data->data, data->dataSize), buildOpts }; std::hash hash_fn; return hash_fn(v); } // Control kernel cache test // // In: // none // // Out: // none // // Returns: // true if kernel cache test is on; false otherwise // bool KernelCache::internalKCacheTestSwitch() { return false; } // Generate file path from a hash value // // In: // hashVal - A hash value // // Out: // pathToFile - Path to the file // // Returns: // none // void KernelCache::getFilePathFromHash(const unsigned int hashVal, std::string &pathToFile) { char textHash[9]; sprintf(textHash, "%08x", hashVal); std::string fileName = textHash; pathToFile = rootPath; pathToFile += amd::Os::fileSeparator(); // First char determines first dir level pathToFile += fileName[0]; pathToFile += amd::Os::fileSeparator(); // Second char determines second dir level pathToFile += fileName[1]; pathToFile += amd::Os::fileSeparator(); // Rest of file name determines name pathToFile += fileName.c_str() + 2; } // Use data and buildOpts to generate a file name // // In: // data - Pointer to data // buildOpts - Build options // // Out: // pathToFile - Path to the file // // Returns: // none // void KernelCache::makeFileName(const KernelCacheData *data, const std::string &buildOpts, std::string &pathToFile) { unsigned int hashVal = computeHash(data, buildOpts); getFilePathFromHash(hashVal, pathToFile); } // Verify whether the file includes the right kernel cache file header // // In: // H - Kernel cache file header // buildOpts - Build options // // Out: // None // // Returns: // true if the file is the one matched our requirement; false othereise // bool KernelCache::verifyKernelCacheFileHeader(KernelCacheFileHeader &H, const std::string &buildOpts) { const char AMD[4] = {'A', 'M', 'D', '\0'}; if (memcmp(H.AMD, AMD, 4)) { errorMsg = "Not a valid cache file"; return false; } if (H.buildOptSize != buildOpts.size()) { errorMsg = "Cache collision: Build option lengths do not match"; return false; } return true; } // Read contents in cacheFile // // In: // cacheFile - cache file to be read // sizeToRead - total bytes to be read // // Out: // buffer - contains file content // // Returns: // true if file reads succeed; false otherwise // bool KernelCache::readFile(FileHandle cacheFile, void *buffer, ssize_t sizeToRead) { // Read content to the buffer #if _WIN32 DWORD bytesRead = 0; if (FALSE == ReadFile(cacheFile, buffer, sizeToRead, &bytesRead, NULL)) { logErrorCloseFile("Unable to read cache file: " + getLastErrorMsg(), cacheFile); return false; } #else ssize_t bytesRead = read(cacheFile, buffer, sizeToRead); #endif // Check if there is any error in file reading if (bytesRead != sizeToRead) { logErrorCloseFile("Error reading cache file: " + getLastErrorMsg(), cacheFile); return false; } return true; } // Write contents to cacheFile // // In: // cacheFile - cache file to be written // buffer - contains content to be written // sizeToWriten - total bytes to be written // // Out: // none // // Returns: // true if file writes succeed; false otherwise // bool KernelCache::writeFile(FileHandle cacheFile, const void *buffer, ssize_t sizeToWritten) { #if _WIN32 DWORD bytesWritten = 0; if (FALSE == WriteFile(cacheFile, buffer, sizeToWritten, &bytesWritten, NULL)) { logErrorCloseFile("Unable to write to file: " + getLastErrorMsg(), cacheFile); return false; } #else ssize_t bytesWritten = write(cacheFile, buffer, sizeToWritten); #endif // Check if there is any error in file reading if (bytesWritten != sizeToWritten) { logErrorCloseFile("Error writing cache file: " + getLastErrorMsg(), cacheFile); return false; } return true; } // Open a file and write its contents // // In: // fileName - Path to file // data - Pointer to file contents // size - Data size // // Out: // none // // Returns: // true if the file is written to file successfully; false otherwise // bool KernelCache::writeFile(const std::string &fileName, const void *data, size_t size, bool appendable) { #if _WIN32 DWORD appendAccess = 0; if (appendable) { appendAccess = FILE_APPEND_DATA; } HANDLE cacheFile = CreateFile(fileName.c_str(), GENERIC_WRITE | WRITE_OWNER | READ_CONTROL | appendAccess, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (cacheFile == INVALID_HANDLE_VALUE) { errorMsg = "Error opening file for writing: " + getLastErrorMsg(); return false; } #else int cacheFile = -1; if (appendable) { cacheFile = open(fileName.c_str(), O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); } else { cacheFile = open(fileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); } if (cacheFile == -1) { errorMsg = "Error opening file for writing: " + getLastErrorMsg(); return false; } // Exclusive write lock for cache file struct flock fl = {F_WRLCK, SEEK_SET, 0, 0}; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error setting file write lock: " + getLastErrorMsg(), cacheFile); return false; } #endif // Write data to file if (!writeFile(cacheFile, data, size)) { removePartiallyWrittenFile(fileName); return false; } #if __linux__ // Unlock the file fl.l_type = F_UNLCK; if (fcntl(cacheFile, F_SETLK, &fl) == -1) { logErrorCloseFile("Error unlock file write lock: " + getLastErrorMsg(), cacheFile); return false; } #endif CloseFile(cacheFile); // Set file to only owner accessible return setAccessPermission(fileName, true); } // Remove file // // In: // fileName - Path to file // // Out: // none // // Returns: // none // void KernelCache::removePartiallyWrittenFile(const std::string &fileName) { errorMsg = getLastErrorMsg(); #if _WIN32 if (!DeleteFile(fileName.c_str())) { #else if (remove(fileName.c_str())) { #endif errorMsg += ", Unable to delete partially written cache file: " + getLastErrorMsg(); } } // Log caching error messages for debugging the cache and/or detecting collisions // // In: // extraMsg - Extra message // // Out: // none // // Returns: // none // void KernelCache::appendLogToFile(std::string extraMsg) { if (amd::Os::pathExists(rootPath)) { std::string fileName = rootPath + amd::Os::fileSeparator() + "cacheError.log"; errorMsg += extraMsg; if ('\n' != errorMsg[errorMsg.size()-1]) { errorMsg.append("\n"); } writeFile(fileName, errorMsg.c_str(), errorMsg.length(), true); } } // Log error message and close the file // // In: // errorMsg - Error message // file - file handle // // Out: // none // // Returns: // none // void KernelCache::logErrorCloseFile(const std::string &errorMsg, const FileHandle file) { appendLogToFile(errorMsg); CloseFile(file); }