blob: 9835d2b4dc457b56ed9b5ba00a5e12c15b0b8a09 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "lang_id/common/file/mmap.h"
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#ifdef _WIN32
#include <winbase.h>
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
#include <sys/stat.h>
#include "lang_id/common/lite_base/logging.h"
#include "lang_id/common/lite_base/macros.h"
namespace libtextclassifier3 {
namespace mobile {
namespace {
inline MmapHandle GetErrorMmapHandle() { return MmapHandle(nullptr, 0); }
} // anonymous namespace
#ifdef _WIN32
namespace {
inline std::string GetLastSystemError() {
LPTSTR message_buffer;
DWORD error_code = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&message_buffer, 0, NULL);
std::string result(message_buffer);
LocalFree(message_buffer);
return result;
}
// Class for automatically closing a Win32 HANDLE on exit from a scope.
class Win32HandleCloser {
public:
explicit Win32HandleCloser(HANDLE handle) : handle_(handle) {}
~Win32HandleCloser() {
bool result = CloseHandle(handle_);
if (!result) {
const DWORD last_error = GetLastError();
SAFTM_LOG(ERROR) << "Error closing handle: " << last_error << ": "
<< GetLastSystemError();
}
}
private:
const HANDLE handle_;
SAFTM_DISALLOW_COPY_AND_ASSIGN(Win32HandleCloser);
};
} // namespace
MmapHandle MmapFile(const std::string &filename) {
HANDLE handle =
CreateFile(filename.c_str(), // File to open.
GENERIC_READ, // Open for reading.
FILE_SHARE_READ, // Share for reading.
NULL, // Default security.
OPEN_EXISTING, // Existing file only.
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // Normal file.
NULL); // No attr. template.
if (handle == INVALID_HANDLE_VALUE) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error opening " << filename << ": " << last_error;
return GetErrorMmapHandle();
}
// Make sure we close handle no matter how we exit this function.
Win32HandleCloser handle_closer(handle);
return MmapFile(handle);
}
MmapHandle MmapFile(HANDLE file_handle) {
// Get the file size.
DWORD file_size_high = 0;
DWORD file_size_low = GetFileSize(file_handle, &file_size_high);
if (file_size_low == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Unable to stat fd: " << last_error;
return GetErrorMmapHandle();
}
size_t file_size_in_bytes = (static_cast<size_t>(file_size_high) << 32) +
static_cast<size_t>(file_size_low);
// Create a file mapping object that refers to the file.
HANDLE file_mapping_object =
CreateFileMappingA(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (file_mapping_object == NULL) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error while mmapping: " << last_error;
return GetErrorMmapHandle();
}
Win32HandleCloser handle_closer(file_mapping_object);
// Map the file mapping object into memory.
void *mmap_addr =
MapViewOfFile(file_mapping_object, FILE_MAP_READ, 0, 0, // File offset.
0 // Number of bytes to map; 0 means map the whole file.
);
if (mmap_addr == nullptr) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error while mmapping: " << last_error;
return GetErrorMmapHandle();
}
return MmapHandle(mmap_addr, file_size_in_bytes);
}
bool Unmap(MmapHandle mmap_handle) {
if (!mmap_handle.ok()) {
// Unmapping something that hasn't been mapped is trivially successful.
return true;
}
bool succeeded = UnmapViewOfFile(mmap_handle.start());
if (!succeeded) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error during Unmap / UnmapViewOfFile: " << last_error;
return false;
}
return true;
}
#else
namespace {
inline std::string GetLastSystemError() { return std::string(strerror(errno)); }
class FileCloser {
public:
explicit FileCloser(int fd) : fd_(fd) {}
~FileCloser() {
int result = close(fd_);
if (result != 0) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error closing file descriptor: " << last_error;
}
}
private:
const int fd_;
SAFTM_DISALLOW_COPY_AND_ASSIGN(FileCloser);
};
} // namespace
MmapHandle MmapFile(const std::string &filename) {
int fd = open(filename.c_str(), O_RDONLY);
if (fd < 0) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error opening " << filename << ": " << last_error;
return GetErrorMmapHandle();
}
// Make sure we close fd no matter how we exit this function. As the man page
// for mmap clearly states: "closing the file descriptor does not unmap the
// region." Hence, we can close fd as soon as we return from here.
FileCloser file_closer(fd);
return MmapFile(fd);
}
MmapHandle MmapFile(int fd) {
// Get file stats to obtain file size.
struct stat sb;
if (fstat(fd, &sb) != 0) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Unable to stat fd: " << last_error;
return GetErrorMmapHandle();
}
size_t file_size_in_bytes = static_cast<size_t>(sb.st_size);
// Perform actual mmap.
return MmapFile(fd, /*offset_in_bytes=*/0, file_size_in_bytes);
}
MmapHandle MmapFile(int fd, size_t offset_in_bytes, size_t size_in_bytes) {
// Make sure the offset is a multiple of the page size, as returned by
// sysconf(_SC_PAGE_SIZE); this is required by the man-page for mmap.
static const size_t kPageSize = sysconf(_SC_PAGE_SIZE);
const size_t aligned_offset = (offset_in_bytes / kPageSize) * kPageSize;
const size_t alignment_shift = offset_in_bytes - aligned_offset;
const size_t aligned_length = size_in_bytes + alignment_shift;
void *mmap_addr = mmap(
// Let system pick address for mmapp-ed data.
nullptr,
aligned_length,
// One can read / write the mapped data (but see MAP_PRIVATE below).
// Normally, we expect only to read it, but in the future, we may want to
// write it, to fix e.g., endianness differences.
PROT_READ | PROT_WRITE,
// Updates to mmaped data are *not* propagated to actual file.
// AFAIK(salcianu) that's anyway not possible on Android.
MAP_PRIVATE,
// Descriptor of file to mmap.
fd,
aligned_offset);
if (mmap_addr == MAP_FAILED) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error while mmapping: " << last_error;
return GetErrorMmapHandle();
}
return MmapHandle(static_cast<char *>(mmap_addr) + alignment_shift,
size_in_bytes);
}
bool Unmap(MmapHandle mmap_handle) {
if (!mmap_handle.ok()) {
// Unmapping something that hasn't been mapped is trivially successful.
return true;
}
if (munmap(mmap_handle.start(), mmap_handle.num_bytes()) != 0) {
const std::string last_error = GetLastSystemError();
SAFTM_LOG(ERROR) << "Error during Unmap / munmap: " << last_error;
return false;
}
return true;
}
#endif
} // namespace mobile
} // namespace nlp_saft