|
#define _LARGEFILE64_SOURCE |
|
#define _FILE_OFFSET_BITS 64 |
|
|
|
#include "file.hh" |
|
|
|
#include "exception.hh" |
|
|
|
#include <algorithm> |
|
#include <cstdlib> |
|
#include <cstdio> |
|
#include <iostream> |
|
#include <limits> |
|
#include <stdexcept> |
|
#include <sstream> |
|
|
|
#include <cassert> |
|
#include <cerrno> |
|
#include <climits> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#include <stdint.h> |
|
|
|
#if defined(__MINGW32__) |
|
#include <windows.h> |
|
#include <unistd.h> |
|
#warning "The file functions on MinGW have not been tested for file sizes above 2^31 - 1. Please read https://stackoverflow.com/questions/12539488/determine-64-bit-file-size-in-c-on-mingw-32-bit and fix" |
|
#elif defined(_WIN32) || defined(_WIN64) |
|
#include <windows.h> |
|
#include <io.h> |
|
#else |
|
#include <unistd.h> |
|
#endif |
|
|
|
namespace util { |
|
|
|
scoped_fd::~scoped_fd() { |
|
if (fd_ != -1 && close(fd_)) { |
|
std::cerr << "Could not close file " << fd_ << std::endl; |
|
std::abort(); |
|
} |
|
} |
|
|
|
void scoped_FILE_closer::Close(std::FILE *file) { |
|
if (file && std::fclose(file)) { |
|
std::cerr << "Could not close file " << file << std::endl; |
|
std::abort(); |
|
} |
|
} |
|
|
|
|
|
FDException::FDException(int fd) throw() : fd_(fd), name_guess_(NameFromFD(fd)) { |
|
*this << "in " << name_guess_ << ' '; |
|
} |
|
|
|
FDException::~FDException() throw() {} |
|
|
|
EndOfFileException::EndOfFileException() throw() { |
|
*this << "End of file"; |
|
} |
|
EndOfFileException::~EndOfFileException() throw() {} |
|
|
|
bool InputFileIsStdin(StringPiece path) { |
|
return path == "-" || path == "/dev/stdin"; |
|
} |
|
|
|
bool OutputFileIsStdout(StringPiece path) { |
|
return path == "-" || path == "/dev/stdout"; |
|
} |
|
|
|
int OpenReadOrThrow(const char *name) { |
|
int ret; |
|
#if defined(_WIN32) || defined(_WIN64) |
|
UTIL_THROW_IF(-1 == (ret = _open(name, _O_BINARY | _O_RDONLY)), ErrnoException, "while opening " << name); |
|
#else |
|
UTIL_THROW_IF(-1 == (ret = open(name, O_RDONLY)), ErrnoException, "while opening " << name); |
|
#endif |
|
return ret; |
|
} |
|
|
|
int CreateOrThrow(const char *name) { |
|
int ret; |
|
#if defined(_WIN32) || defined(_WIN64) |
|
UTIL_THROW_IF(-1 == (ret = _open(name, _O_CREAT | _O_TRUNC | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE)), ErrnoException, "while creating " << name); |
|
#else |
|
UTIL_THROW_IF(-1 == (ret = open(name, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)), ErrnoException, "while creating " << name); |
|
#endif |
|
return ret; |
|
} |
|
|
|
uint64_t SizeFile(int fd) { |
|
#if defined __MINGW32__ |
|
struct stat sb; |
|
|
|
int ret = fstat(fd, &sb); |
|
if (ret == -1 || (!sb.st_size && !S_ISREG(sb.st_mode))) return kBadSize; |
|
return sb.st_size; |
|
#elif defined(_WIN32) || defined(_WIN64) |
|
__int64 ret = _filelengthi64(fd); |
|
return (ret == -1) ? kBadSize : ret; |
|
#else |
|
|
|
#ifdef OS_ANDROID |
|
struct stat64 sb; |
|
int ret = fstat64(fd, &sb); |
|
#else |
|
struct stat sb; |
|
int ret = fstat(fd, &sb); |
|
#endif |
|
if (ret == -1 || (!sb.st_size && !S_ISREG(sb.st_mode))) return kBadSize; |
|
return sb.st_size; |
|
#endif |
|
} |
|
|
|
uint64_t SizeOrThrow(int fd) { |
|
uint64_t ret = SizeFile(fd); |
|
UTIL_THROW_IF_ARG(ret == kBadSize, FDException, (fd), "Failed to size"); |
|
return ret; |
|
} |
|
|
|
void ResizeOrThrow(int fd, uint64_t to) { |
|
#if defined __MINGW32__ |
|
|
|
int ret = ftruncate |
|
#elif defined(_WIN32) || defined(_WIN64) |
|
errno_t ret = _chsize_s |
|
#elif defined(OS_ANDROID) |
|
int ret = ftruncate64 |
|
#else |
|
int ret = ftruncate |
|
#endif |
|
(fd, to); |
|
UTIL_THROW_IF_ARG(ret, FDException, (fd), "while resizing to " << to << " bytes"); |
|
} |
|
|
|
void HolePunch(int fd, uint64_t offset, uint64_t size) { |
|
#if defined(__linux__) && defined(FALLOC_FL_PUNCH_HOLE) && defined(FALLOC_FL_KEEP_SIZE) |
|
UTIL_THROW_IF_ARG(-1 == fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, size), FDException, (fd), "in punching a hole at " << offset << " for " << size << " bytes."); |
|
#else |
|
UTIL_THROW(UnsupportedOSException, "fallocate hole punching requires Linux and glibc >= 2.18"); |
|
#endif |
|
} |
|
|
|
namespace { |
|
std::size_t GuardLarge(std::size_t size) { |
|
|
|
|
|
|
|
|
|
#if defined(_WIN32) || defined(_WIN64) || defined(__APPLE__) || defined(OS_ANDROID) || defined(__MINGW32__) |
|
return size < INT_MAX ? size : INT_MAX; |
|
#else |
|
return size; |
|
#endif |
|
} |
|
} |
|
|
|
#if defined(_WIN32) || defined(_WIN64) |
|
namespace { |
|
const std::size_t kMaxDWORD = static_cast<std::size_t>(4294967295UL); |
|
} |
|
#endif |
|
|
|
std::size_t PartialRead(int fd, void *to, std::size_t amount) { |
|
#if defined(_WIN32) || defined(_WIN64) |
|
DWORD ret; |
|
HANDLE file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
|
DWORD larger_size = static_cast<DWORD>(std::min<std::size_t>(kMaxDWORD, amount)); |
|
DWORD smaller_size = 28672; |
|
if (!ReadFile(file_handle, to, larger_size, &ret, NULL)) |
|
{ |
|
DWORD last_error = GetLastError(); |
|
if (last_error != ERROR_NOT_ENOUGH_MEMORY || !ReadFile(file_handle, to, smaller_size, &ret, NULL)) { |
|
UTIL_THROW(WindowsException, "Windows error in ReadFile."); |
|
} |
|
} |
|
#else |
|
errno = 0; |
|
ssize_t ret; |
|
do { |
|
ret = read(fd, to, GuardLarge(amount)); |
|
} while (ret == -1 && errno == EINTR); |
|
UTIL_THROW_IF_ARG(ret < 0, FDException, (fd), "while reading " << amount << " bytes"); |
|
#endif |
|
return static_cast<std::size_t>(ret); |
|
} |
|
|
|
void ReadOrThrow(int fd, void *to_void, std::size_t amount) { |
|
uint8_t *to = static_cast<uint8_t*>(to_void); |
|
while (amount) { |
|
std::size_t ret = PartialRead(fd, to, amount); |
|
UTIL_THROW_IF(ret == 0, EndOfFileException, " in " << NameFromFD(fd) << " but there should be " << amount << " more bytes to read."); |
|
amount -= ret; |
|
to += ret; |
|
} |
|
} |
|
|
|
std::size_t ReadOrEOF(int fd, void *to_void, std::size_t amount) { |
|
uint8_t *to = static_cast<uint8_t*>(to_void); |
|
std::size_t remaining = amount; |
|
while (remaining) { |
|
std::size_t ret = PartialRead(fd, to, remaining); |
|
if (!ret) return amount - remaining; |
|
remaining -= ret; |
|
to += ret; |
|
} |
|
return amount; |
|
} |
|
|
|
void WriteOrThrow(int fd, const void *data_void, std::size_t size) { |
|
const uint8_t *data = static_cast<const uint8_t*>(data_void); |
|
while (size) { |
|
#if defined(_WIN32) || defined(_WIN64) |
|
int ret; |
|
#else |
|
ssize_t ret; |
|
#endif |
|
errno = 0; |
|
do { |
|
ret = |
|
#if defined(_WIN32) || defined(_WIN64) |
|
_write |
|
#else |
|
write |
|
#endif |
|
(fd, data, GuardLarge(size)); |
|
} while (ret == -1 && errno == EINTR); |
|
UTIL_THROW_IF_ARG(ret < 1, FDException, (fd), "while writing " << size << " bytes"); |
|
data += ret; |
|
size -= ret; |
|
} |
|
} |
|
|
|
void WriteOrThrow(FILE *to, const void *data, std::size_t size) { |
|
if (!size) return; |
|
UTIL_THROW_IF(1 != std::fwrite(data, size, 1, to), ErrnoException, "Short write; requested size " << size); |
|
} |
|
|
|
void ErsatzPRead(int fd, void *to_void, std::size_t size, uint64_t off) { |
|
uint8_t *to = static_cast<uint8_t*>(to_void); |
|
while (size) { |
|
#if defined(_WIN32) || defined(_WIN64) |
|
|
|
|
|
DWORD reading = static_cast<DWORD>(std::min<std::size_t>(kMaxDWORD, size)); |
|
DWORD ret; |
|
OVERLAPPED overlapped; |
|
memset(&overlapped, 0, sizeof(OVERLAPPED)); |
|
overlapped.Offset = static_cast<DWORD>(off); |
|
overlapped.OffsetHigh = static_cast<DWORD>(off >> 32); |
|
UTIL_THROW_IF(!ReadFile((HANDLE)_get_osfhandle(fd), to, reading, &ret, &overlapped), WindowsException, "ReadFile failed for offset " << off); |
|
#else |
|
ssize_t ret; |
|
errno = 0; |
|
ret = |
|
#ifdef OS_ANDROID |
|
pread64 |
|
#else |
|
pread |
|
#endif |
|
(fd, to, GuardLarge(size), off); |
|
if (ret <= 0) { |
|
if (ret == -1 && errno == EINTR) continue; |
|
UTIL_THROW_IF(ret == 0, EndOfFileException, " for reading " << size << " bytes at " << off << " from " << NameFromFD(fd)); |
|
UTIL_THROW_ARG(FDException, (fd), "while reading " << size << " bytes at offset " << off); |
|
} |
|
#endif |
|
size -= ret; |
|
off += ret; |
|
to += ret; |
|
} |
|
} |
|
|
|
void ErsatzPWrite(int fd, const void *from_void, std::size_t size, uint64_t off) { |
|
const uint8_t *from = static_cast<const uint8_t*>(from_void); |
|
while(size) { |
|
#if defined(_WIN32) || defined(_WIN64) |
|
|
|
|
|
DWORD writing = static_cast<DWORD>(std::min<std::size_t>(kMaxDWORD, size)); |
|
DWORD ret; |
|
OVERLAPPED overlapped; |
|
memset(&overlapped, 0, sizeof(OVERLAPPED)); |
|
overlapped.Offset = static_cast<DWORD>(off); |
|
overlapped.OffsetHigh = static_cast<DWORD>(off >> 32); |
|
UTIL_THROW_IF(!WriteFile((HANDLE)_get_osfhandle(fd), from, writing, &ret, &overlapped), Exception, "WriteFile failed for offset " << off); |
|
#else |
|
ssize_t ret; |
|
errno = 0; |
|
ret = |
|
#ifdef OS_ANDROID |
|
pwrite64 |
|
#else |
|
pwrite |
|
#endif |
|
(fd, from, GuardLarge(size), off); |
|
if (ret <= 0) { |
|
if (ret == -1 && errno == EINTR) continue; |
|
UTIL_THROW_IF(ret == 0, EndOfFileException, " for writing " << size << " bytes at " << off << " from " << NameFromFD(fd)); |
|
UTIL_THROW_ARG(FDException, (fd), "while writing " << size << " bytes at offset " << off); |
|
} |
|
#endif |
|
size -= ret; |
|
off += ret; |
|
from += ret; |
|
} |
|
} |
|
|
|
|
|
void FSyncOrThrow(int fd) { |
|
|
|
#if !defined(_WIN32) && !defined(_WIN64) |
|
UTIL_THROW_IF_ARG(-1 == fsync(fd), FDException, (fd), "while syncing"); |
|
#endif |
|
} |
|
|
|
namespace { |
|
|
|
|
|
#if !defined(_WIN32) && !defined(_WIN64) && !defined(OS_ANDROID) |
|
template <unsigned> struct CheckOffT; |
|
template <> struct CheckOffT<8> { |
|
struct True {}; |
|
}; |
|
|
|
|
|
typedef CheckOffT<sizeof(off_t)>::True IgnoredType; |
|
#endif |
|
|
|
|
|
uint64_t InternalSeek(int fd, int64_t off, int whence) { |
|
#if defined __MINGW32__ |
|
|
|
typedef off_t Offset; |
|
Offset ret = lseek(fd, off, whence); |
|
#elif defined(_WIN32) || defined(_WIN64) |
|
typedef __int64 Offset; |
|
Offset ret = _lseeki64(fd, off, whence); |
|
#elif defined(OS_ANDROID) |
|
typedef off64_t Offset; |
|
Offset ret = lseek64(fd, off, whence); |
|
#else |
|
typedef off_t Offset; |
|
Offset ret = lseek(fd, off, whence); |
|
#endif |
|
UTIL_THROW_IF_ARG((Offset)-1 == ret, FDException, (fd), "while seeking to " << off << " whence " << whence); |
|
return (uint64_t)ret; |
|
} |
|
} |
|
|
|
uint64_t SeekOrThrow(int fd, uint64_t off) { |
|
return InternalSeek(fd, off, SEEK_SET); |
|
} |
|
|
|
uint64_t AdvanceOrThrow(int fd, int64_t off) { |
|
return InternalSeek(fd, off, SEEK_CUR); |
|
} |
|
|
|
uint64_t SeekEnd(int fd) { |
|
return InternalSeek(fd, 0, SEEK_END); |
|
} |
|
|
|
std::FILE *FDOpenOrThrow(scoped_fd &file) { |
|
std::FILE *ret = fdopen(file.get(), "r+b"); |
|
UTIL_THROW_IF_ARG(!ret, FDException, (file.get()), "Could not fdopen for write"); |
|
file.release(); |
|
return ret; |
|
} |
|
|
|
std::FILE *FDOpenReadOrThrow(scoped_fd &file) { |
|
std::FILE *ret = fdopen(file.get(), "rb"); |
|
UTIL_THROW_IF_ARG(!ret, FDException, (file.get()), "Could not fdopen for read"); |
|
file.release(); |
|
return ret; |
|
} |
|
|
|
|
|
#if defined(_WIN32) || defined(_WIN64) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const char letters[] = |
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
|
|
|
|
|
|
|
|
|
|
|
int |
|
mkstemp_and_unlink(char *tmpl) |
|
{ |
|
int len; |
|
char *XXXXXX; |
|
static unsigned long long value; |
|
unsigned long long random_time_bits; |
|
unsigned int count; |
|
int fd = -1; |
|
int save_errno = errno; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define ATTEMPTS_MIN (62 * 62 * 62) |
|
|
|
|
|
|
|
#if ATTEMPTS_MIN < TMP_MAX |
|
unsigned int attempts = TMP_MAX; |
|
#else |
|
unsigned int attempts = ATTEMPTS_MIN; |
|
#endif |
|
|
|
len = strlen (tmpl); |
|
if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) |
|
{ |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
|
|
XXXXXX = &tmpl[len - 6]; |
|
|
|
|
|
{ |
|
SYSTEMTIME stNow; |
|
FILETIME ftNow; |
|
|
|
|
|
GetSystemTime(&stNow); |
|
stNow.wMilliseconds = 500; |
|
if (!SystemTimeToFileTime(&stNow, &ftNow)) |
|
{ |
|
errno = -1; |
|
return -1; |
|
} |
|
|
|
random_time_bits = (((unsigned long long)ftNow.dwHighDateTime << 32) |
|
| (unsigned long long)ftNow.dwLowDateTime); |
|
} |
|
value += random_time_bits ^ (unsigned long long)GetCurrentThreadId (); |
|
|
|
for (count = 0; count < attempts; value += 7777, ++count) |
|
{ |
|
unsigned long long v = value; |
|
|
|
|
|
XXXXXX[0] = letters[v % 62]; |
|
v /= 62; |
|
XXXXXX[1] = letters[v % 62]; |
|
v /= 62; |
|
XXXXXX[2] = letters[v % 62]; |
|
v /= 62; |
|
XXXXXX[3] = letters[v % 62]; |
|
v /= 62; |
|
XXXXXX[4] = letters[v % 62]; |
|
v /= 62; |
|
XXXXXX[5] = letters[v % 62]; |
|
|
|
|
|
|
|
int flags = _O_RDWR | _O_CREAT | _O_EXCL | _O_BINARY; |
|
flags |= _O_TEMPORARY; |
|
fd = _open (tmpl, flags, _S_IREAD | _S_IWRITE); |
|
if (fd >= 0) |
|
{ |
|
errno = save_errno; |
|
return fd; |
|
} |
|
else if (errno != EEXIST) |
|
return -1; |
|
} |
|
|
|
|
|
errno = EEXIST; |
|
return -1; |
|
} |
|
#else |
|
int |
|
mkstemp_and_unlink(char *tmpl) { |
|
int ret = mkstemp(tmpl); |
|
if (ret != -1) { |
|
UTIL_THROW_IF(unlink(tmpl), ErrnoException, "while deleting " << tmpl); |
|
} |
|
return ret; |
|
} |
|
#endif |
|
|
|
|
|
|
|
void NormalizeTempPrefix(std::string &base) { |
|
if (base.empty()) return; |
|
if (base[base.size() - 1] == '/') return; |
|
struct stat sb; |
|
|
|
if (-1 == stat(base.c_str(), &sb)) return; |
|
if ( |
|
#if defined(_WIN32) || defined(_WIN64) |
|
sb.st_mode & _S_IFDIR |
|
#else |
|
S_ISDIR(sb.st_mode) |
|
#endif |
|
) base += '/'; |
|
} |
|
|
|
int MakeTemp(const StringPiece &base) { |
|
std::string name(base.data(), base.size()); |
|
name += "XXXXXX"; |
|
name.push_back(0); |
|
int ret; |
|
UTIL_THROW_IF(-1 == (ret = mkstemp_and_unlink(&name[0])), ErrnoException, "while making a temporary based on " << base); |
|
return ret; |
|
} |
|
|
|
std::FILE *FMakeTemp(const StringPiece &base) { |
|
util::scoped_fd file(MakeTemp(base)); |
|
return FDOpenOrThrow(file); |
|
} |
|
|
|
std::string DefaultTempDirectory() { |
|
#if defined(_WIN32) || defined(_WIN64) |
|
char dir_buffer[1000]; |
|
if (GetTempPath(1000, dir_buffer) == 0) |
|
throw std::runtime_error("Could not read temporary directory."); |
|
std::string ret(dir_buffer); |
|
NormalizeTempPrefix(ret); |
|
return ret; |
|
#else |
|
|
|
const char *const vars[] = {"TMPDIR", "TMP", "TEMPDIR", "TEMP", 0}; |
|
for (int i=0; vars[i]; ++i) { |
|
char *val = |
|
#if defined(_GNU_SOURCE) && defined(__GLIBC_PREREQ) |
|
#if __GLIBC_PREREQ(2,17) |
|
secure_getenv |
|
#else |
|
getenv |
|
#endif |
|
#else |
|
getenv |
|
#endif |
|
(vars[i]); |
|
|
|
if (val && *val) { |
|
std::string ret(val); |
|
NormalizeTempPrefix(ret); |
|
return ret; |
|
} |
|
} |
|
|
|
return "/tmp/"; |
|
#endif |
|
} |
|
|
|
int DupOrThrow(int fd) { |
|
int ret = dup(fd); |
|
UTIL_THROW_IF_ARG(ret == -1, FDException, (fd), "in duplicating the file descriptor"); |
|
return ret; |
|
} |
|
|
|
namespace { |
|
|
|
bool TryName(int fd, std::string &out) { |
|
#if defined(_WIN32) || defined(_WIN64) |
|
return false; |
|
#else |
|
std::string name("/proc/self/fd/"); |
|
std::ostringstream convert; |
|
convert << fd; |
|
name += convert.str(); |
|
|
|
struct stat sb; |
|
if (-1 == lstat(name.c_str(), &sb)) |
|
return false; |
|
out.resize(sb.st_size + 1); |
|
|
|
while (true) { |
|
ssize_t ret = readlink(name.c_str(), &out[0], out.size()); |
|
if (-1 == ret) |
|
return false; |
|
if ((size_t)ret < out.size()) { |
|
out.resize(ret); |
|
break; |
|
} |
|
|
|
out.resize(out.size() * 2); |
|
} |
|
|
|
if (!out.empty() && out[0] != '/') |
|
return false; |
|
return true; |
|
#endif |
|
} |
|
} |
|
|
|
std::string NameFromFD(int fd) { |
|
std::string ret; |
|
if (TryName(fd, ret)) return ret; |
|
switch (fd) { |
|
case 0: return "stdin"; |
|
case 1: return "stdout"; |
|
case 2: return "stderr"; |
|
} |
|
ret = "fd "; |
|
std::ostringstream convert; |
|
convert << fd; |
|
ret += convert.str(); |
|
return ret; |
|
} |
|
|
|
} |
|
|