/*
PIP - Platform Independent Primitives
File
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
*/
#include "pifile.h"
#include "pidir.h"
#include "piincludes_p.h"
#include "piiostream.h"
#include "pitime_win.h"
#include "pitranslator.h"
#ifdef WINDOWS
# undef S_IFDIR
# undef S_IFREG
# undef S_IFLNK
# undef S_IFBLK
# undef S_IFCHR
# undef S_IFSOCK
# define S_IFDIR 0x01
# define S_IFREG 0x02
# define S_IFLNK 0x04
# define S_IFBLK 0x08
# define S_IFCHR 0x10
# define S_IFSOCK 0x20
#else
# include
# include
# include
# include
#endif
#define S_IFHDN 0x40
#if defined(QNX) || defined(ANDROID) || defined(FREERTOS)
# define _fopen_call_ fopen
# define _fseek_call_ fseek
# define _ftell_call_ ftell
# define _stat_struct_ struct stat
# define _stat_call_ stat
# define _stat_link_ lstat
#else
# if defined(MAC_OS)
# define _fopen_call_ fopen
# define _fseek_call_ fseek
# define _ftell_call_ ftell
# else
# ifdef CC_GCC
# define _fopen_call_ fopen64
# define _fseek_call_ fseeko64
# define _ftell_call_ ftello64
# else
# define _fopen_call_ fopen
# define _fseek_call_ fseek
# define _ftell_call_ ftell
# endif
# endif
# define _stat_struct_ struct stat64
# define _stat_call_ stat64
# define _stat_link_ lstat64
#endif
//! \class PIFile pifile.h
//! \details
//! \~english \section PIFile_sec0 Synopsis
//! \~russian \section PIFile_sec0 Краткий обзор
//! \~english
//!
//! This class provide access to local file. You can manipulate
//! binary content or use this class as text stream. To binary
//! access there are function \a read(), \a write(), and many
//! \a writeBinary() functions. For write and read variables to file in
//! their text representation there are many "<<" and ">>" operators.
//!
//! \~russian
//! Этот класс предоставляет доступ к локальному файлу. Можно
//! работать на байтовом уровне, либо использовать его как
//! текстовый поток. Для байтового доступа используются методы
//! \a read(), \a write(), и много \a writeBinary() методов.
//! Для записи и чтения переменных в текстовом представлении
//! используются операторы "<<" и ">>".
//!
//! \~english \section PIFile_sec1 Position
//! \~russian \section PIFile_sec1 Позиция
//! \~english
//! Each opened file has a read/write position - logical position
//! in the file content you read from or you write to. You can
//! find out current position with function \a pos(). Function
//! \a seek(llong position) move position to position "position",
//! \a seekToBegin() move position to the begin of file,
//! \a seekToEnd() move position to the end of file.
//!
//! \~russian
//! Каждый файл имеет позицию чтения/записи - логическое положение
//! в содержимом файла, откуда производится чтение или запись.
//! Узнать текущую позицию можно с помощью метода \a pos().
//! Метод \a seek(llong position) перемещает позицию на указанную,
//! \a seekToBegin() перемещает её в начало файла, а \a seekToEnd() - в конец.
//!
REGISTER_DEVICE(PIFile)
PRIVATE_DEFINITION_START(PIFile)
FILE * fd = nullptr;
PRIVATE_DEFINITION_END(PIFile)
PIString PIFile::FileInfo::name() const {
if (path.isEmpty()) return PIString();
return path.mid(path.findLast(PIDir::separator) + 1);
}
PIString PIFile::FileInfo::baseName() const {
if (path.isEmpty()) return PIString();
PIString n = name(), e = extension();
if (e.isEmpty()) return n;
return n.cutRight(e.size_s() + 1);
}
PIString PIFile::FileInfo::extension() const {
PIString n = name();
if (n.isEmpty()) return PIString();
while (n.startsWith("."))
n.pop_front();
if (n.isEmpty()) return PIString();
int i = n.findLast(".");
if (i < 0) return PIString();
return n.mid(i + 1);
}
PIString PIFile::FileInfo::dir() const {
if (path.isEmpty()) return PIString();
int ind = path.findLast(PIDir::separator);
PIString ret;
if (ind >= 0) ret = path.mid(0, ind);
if (ret.isEmpty()) ret = ".";
return ret + PIDir::separator;
}
bool PIFile::FileInfo::isAbsolute() const {
if (path.isEmpty()) return false;
if (path[0] == '/') return true;
if (path.size() >= 2) {
if (path[0].isAlpha() && path[1] == ':') return true;
}
return false;
}
PIFile::PIFile(): PIIODevice() {}
PIFile::PIFile(const PIString & path, PIIODevice::DeviceMode mode): PIIODevice(path, mode) {
if (!path.isEmpty()) open();
}
bool PIFile::openTemporary(PIIODevice::DeviceMode mode) {
PIString tp;
#ifdef WINDOWS
tp = PIDir::temporary().path() + PIDir::separator + "file" + PIString::fromNumber(randomi());
while (isExists(tp)) {
tp += PIString::fromNumber(randomi() % 10);
}
#else
char template_rc[] = "/tmp/pifile_tmp_XXXXXX";
int fd = mkstemp(template_rc);
if (fd == -1) return false;
::close(fd);
tp = template_rc;
#endif
return open(tp, mode);
}
PIFile::~PIFile() {
stop();
close();
}
bool PIFile::openDevice() {
close();
PIString p = path();
if (p.isEmpty()) return false;
if ((mode_ & PIIODevice::WriteOnly) == PIIODevice::WriteOnly) {
if (!isExists(p)) {
FILE * fd = fopen(p.data(), "w");
if (fd != 0) fclose(fd);
}
}
PRIVATE->fd = _fopen_call_(p.data(), strType(mode_).data());
// piCout << "fopen " << path() << ": " << strType(mode_).data() << PRIVATE->fd;
bool opened = (PRIVATE->fd != 0);
if (opened) {
fdi = fileno(PRIVATE->fd);
#ifndef WINDOWS
fcntl(fdi, F_SETFL, O_NONBLOCK);
#endif
if (mode_ == PIIODevice::ReadOnly) {
_fseek_call_(PRIVATE->fd, 0, SEEK_END);
_size = _ftell_call_(PRIVATE->fd);
}
_fseek_call_(PRIVATE->fd, 0, SEEK_SET);
clearerr(PRIVATE->fd);
}
// piCout << "open file" << PRIVATE->fd << opened_;
return opened;
}
bool PIFile::closeDevice() {
// piCout << "close file" << PRIVATE->fd << opened_;
if (isClosed() || PRIVATE->fd == 0) return true;
bool cs = (fclose(PRIVATE->fd) == 0);
if (cs) PRIVATE->fd = 0;
fdi = -1;
_size = -1;
// piCout << "closed file" << PRIVATE->fd << opened_;
return cs;
}
llong PIFile::readAll(void * data) {
llong cp = pos(), s = size(), i = -1;
seekToBegin();
if (s < 0) {
while (!isEnd())
read(&(((char *)data)[++i]), 1);
} else
read((char *)data, s);
seek(cp);
return s;
}
PIByteArray PIFile::readAll(bool forceRead) {
PIByteArray a;
llong cp = pos();
if (forceRead) {
seekToBegin();
for (;;) {
uchar byte = static_cast(fgetc(PRIVATE->fd));
if (feof(PRIVATE->fd) || ferror(PRIVATE->fd)) break;
a.push_back(byte);
}
seek(cp);
return a;
}
llong s = size();
if (s < 0) return a;
a.resize(s);
seekToBegin();
auto _r = fread(a.data(), 1, s, PRIVATE->fd);
NO_UNUSED(_r);
seek(cp);
if (s >= 0) a.resize(s);
return a;
}
llong PIFile::size() const {
if (isClosed()) return -1;
llong s, cp = pos();
_fseek_call_(PRIVATE->fd, 0, SEEK_END);
clearerr(PRIVATE->fd);
s = pos();
_fseek_call_(PRIVATE->fd, cp, SEEK_SET);
clearerr(PRIVATE->fd);
return s;
}
void PIFile::resize(llong new_size, uchar fill_) {
llong ds = new_size - size();
if (ds == 0) return;
if (ds > 0) {
uchar * buff = new uchar[ds];
memset(buff, fill_, ds);
write(buff, ds);
delete[] buff;
return;
}
if (new_size == 0) {
clear();
return;
}
piCoutObj << "Downsize is not supported yet :-("_tr("PIFile");
}
bool PIFile::isExists(const PIString & path) {
FILE * f = _fopen_call_(PIString(path).data(), "r");
bool ok = (f != 0);
if (ok) fclose(f);
return ok;
}
bool PIFile::remove(const PIString & path) {
#ifdef WINDOWS
if (PIDir::isExists(path))
return RemoveDirectoryA(path.data()) > 0;
else
#endif
return ::remove(path.data()) == 0;
}
bool PIFile::rename(const PIString & from, const PIString & to) {
return ::rename(from.data(), to.data()) == 0;
}
PIString PIFile::constructFullPathDevice() const {
return path();
}
void PIFile::configureFromFullPathDevice(const PIString & full_path) {
setPath(full_path);
}
PIPropertyStorage PIFile::constructVariantDevice() const {
PIPropertyStorage ret;
PIVariantTypes::File f;
f.file = path();
ret.addProperty("path", f);
return ret;
}
void PIFile::configureFromVariantDevice(const PIPropertyStorage & d) {
setPath(d.propertyValueByName("path").toString());
}
PIString PIFile::strType(const PIIODevice::DeviceMode type) {
switch (type) {
case PIIODevice::ReadOnly: return "rb";
case PIIODevice::ReadWrite:
case PIIODevice::WriteOnly: return "r+b";
}
return "rb";
}
void PIFile::flush() {
if (isOpened()) fflush(PRIVATE->fd);
}
void PIFile::seek(llong position) {
if (isClosed()) return;
if (position == pos()) return;
_fseek_call_(PRIVATE->fd, position, SEEK_SET);
clearerr(PRIVATE->fd);
}
void PIFile::seekToBegin() {
if (isClosed()) return;
_fseek_call_(PRIVATE->fd, 0, SEEK_SET);
clearerr(PRIVATE->fd);
}
void PIFile::seekToEnd() {
if (isClosed()) return;
_fseek_call_(PRIVATE->fd, 0, SEEK_END);
clearerr(PRIVATE->fd);
}
void PIFile::seekToLine(llong line) {
if (isClosed()) return;
seekToBegin();
PIIOTextStream ts(this);
piForTimes(line) ts.readLine();
clearerr(PRIVATE->fd);
}
void PIFile::skip(llong bytes) {
if (isClosed() || (bytes == 0)) return;
_fseek_call_(PRIVATE->fd, bytes, SEEK_CUR);
}
char PIFile::readChar() {
return (char)fgetc(PRIVATE->fd);
}
void PIFile::setPath(const PIString & path) {
PIIODevice::setPath(path);
if (isOpened()) open();
}
llong PIFile::pos() const {
if (isClosed()) return -1;
return _ftell_call_(PRIVATE->fd);
}
bool PIFile::isEnd() const {
if (isClosed()) return true;
bool ret = (feof(PRIVATE->fd) || ferror(PRIVATE->fd));
if (!ret && (_size >= 0)) {
if (pos() > _size) ret = true;
}
return ret;
}
PIFile & PIFile::put(const PIByteArray & v) {
int sz = (int)v.size_s();
write(createMemoryBlock(&sz));
write(v);
return *this;
}
PIByteArray PIFile::get() {
PIByteArray ret;
int sz(0);
read(createMemoryBlock(&sz));
if (sz > 0) {
ret.resize(sz);
read(ret.data(), sz);
}
return ret;
}
ssize_t PIFile::readDevice(void * read_to, ssize_t max_size) {
if (!canRead() || PRIVATE->fd == 0) return -1;
ssize_t ret = fread(read_to, 1, max_size, PRIVATE->fd);
return ret;
}
ssize_t PIFile::writeDevice(const void * data, ssize_t max_size) {
if (!canWrite() || PRIVATE->fd == 0) return -1;
return fwrite(data, 1, max_size, PRIVATE->fd);
}
void PIFile::clear() {
close();
PRIVATE->fd = fopen(path().data(), "w");
if (PRIVATE->fd != 0) fclose(PRIVATE->fd);
PRIVATE->fd = 0;
opened_ = false;
open();
}
void PIFile::remove() {
close();
::remove(path().data());
}
PIFile::FileInfo PIFile::fileInfo(const PIString & path) {
FileInfo ret;
if (path.isEmpty()) return ret;
ret.path = path.replacedAll("\\", PIDir::separator);
PIString n = ret.name();
// piCout << "open" << path;
#ifdef WINDOWS
DWORD attr = GetFileAttributesA((LPCSTR)(path.data()));
if (attr == 0xFFFFFFFF) return ret;
HANDLE hFile = 0;
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
hFile = CreateFileA(path.data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
} else {
hFile = CreateFileA(path.data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
}
if (!hFile) return ret;
BY_HANDLE_FILE_INFORMATION fi;
piZeroMemory(fi);
if (GetFileInformationByHandle(hFile, &fi) != 0) {
LARGE_INTEGER filesize;
filesize.LowPart = filesize.HighPart = 0;
if (fi.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ret.flags |= FileInfo::Hidden;
if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
ret.flags |= FileInfo::Dir;
else {
ret.flags |= FileInfo::File;
filesize.LowPart = fi.nFileSizeLow;
filesize.HighPart = fi.nFileSizeHigh;
}
PIString ext = ret.extension();
ret.perm_user =
FileInfo::Permissions(true, (attr & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY, ext == "bat" || ext == "exe");
ret.perm_group = ret.perm_other = ret.perm_user;
ret.size = filesize.QuadPart;
ret.time_access = FILETIME2PIDateTime(fi.ftLastAccessTime);
ret.time_modification = FILETIME2PIDateTime(fi.ftLastWriteTime);
}
CloseHandle(hFile);
#else
_stat_struct_ fs;
piZeroMemory(fs);
_stat_call_(path.data(), &fs);
int mode = fs.st_mode;
ret.size = fs.st_size;
ret.id_user = fs.st_uid;
ret.id_group = fs.st_gid;
# ifdef ANDROID
ret.time_access = PIDateTime::fromSystemTime(PISystemTime(fs.st_atime, fs.st_atime_nsec));
ret.time_modification = PIDateTime::fromSystemTime(PISystemTime(fs.st_mtime, fs.st_mtime_nsec));
# else
# if defined(QNX) || defined(FREERTOS)
ret.time_access = PIDateTime::fromSecondSinceEpoch(fs.st_atime);
ret.time_modification = PIDateTime::fromSecondSinceEpoch(fs.st_mtime);
# else
# ifdef MAC_OS
# define ATIME st_atimespec
# define MTIME st_ctimespec
# else
# define ATIME st_atim
# define MTIME st_mtim
# endif
ret.time_access = PIDateTime::fromSystemTime(PISystemTime(fs.ATIME.tv_sec, fs.ATIME.tv_nsec));
ret.time_modification = PIDateTime::fromSystemTime(PISystemTime(fs.MTIME.tv_sec, fs.MTIME.tv_nsec));
# endif
# endif
# ifndef MICRO_PIP
ret.perm_user = FileInfo::Permissions((mode & S_IRUSR) == S_IRUSR, (mode & S_IWUSR) == S_IWUSR, (mode & S_IXUSR) == S_IXUSR);
ret.perm_group = FileInfo::Permissions((mode & S_IRGRP) == S_IRGRP, (mode & S_IWGRP) == S_IWGRP, (mode & S_IXGRP) == S_IXGRP);
ret.perm_other = FileInfo::Permissions((mode & S_IROTH) == S_IROTH, (mode & S_IWOTH) == S_IWOTH, (mode & S_IXOTH) == S_IXOTH);
piZeroMemory(fs);
_stat_link_(path.data(), &fs);
mode &= ~S_IFLNK;
mode |= S_IFLNK & fs.st_mode;
if (n.startsWith(".")) mode |= S_IFHDN;
if ((mode & S_IFDIR) == S_IFDIR) ret.flags |= FileInfo::Dir;
if ((mode & S_IFREG) == S_IFREG) ret.flags |= FileInfo::File;
if ((mode & S_IFLNK) == S_IFLNK) ret.flags |= FileInfo::SymbolicLink;
if ((mode & S_IFHDN) == S_IFHDN) ret.flags |= FileInfo::Hidden;
# endif
#endif
if (n == ".") ret.flags = FileInfo::Dir | FileInfo::Dot;
if (n == "..") ret.flags = FileInfo::Dir | FileInfo::DotDot;
return ret;
}
bool PIFile::applyFileInfo(const PIString & path, const PIFile::FileInfo & info) {
if (path.isEmpty()) return false;
PIString fp(path);
if (fp.endsWith(PIDir::separator)) fp.pop_back();
#ifdef WINDOWS
DWORD attr = GetFileAttributesA((LPCSTR)(path.data()));
if (attr == 0xFFFFFFFF) return false;
attr &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);
if (info.isHidden()) attr |= FILE_ATTRIBUTE_HIDDEN;
if (!info.perm_user.write) attr |= FILE_ATTRIBUTE_READONLY;
if (SetFileAttributesA((LPCSTR)(fp.data()), attr) == 0) {
piCout << "[PIFile] applyFileInfo: \"SetFileAttributes\" error:" << errorString();
}
HANDLE hFile = 0;
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
hFile = CreateFileA(path.data(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
0);
} else {
hFile = CreateFileA(path.data(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
}
if (!hFile) return false;
FILETIME atime = PIDateTime2FILETIME(info.time_access), mtime = PIDateTime2FILETIME(info.time_modification);
if (SetFileTime(hFile, 0, &atime, &mtime) == 0) {
piCout << "[PIFile] applyFileInfo: \"SetFileTime\" error:" << errorString();
return false;
}
CloseHandle(hFile);
#else
int mode(0);
if (info.perm_user.read) mode |= S_IRUSR;
if (info.perm_user.write) mode |= S_IWUSR;
if (info.perm_user.exec) mode |= S_IXUSR;
if (info.perm_group.read) mode |= S_IRGRP;
if (info.perm_group.write) mode |= S_IWGRP;
if (info.perm_group.exec) mode |= S_IXGRP;
if (info.perm_other.read) mode |= S_IROTH;
if (info.perm_other.write) mode |= S_IWOTH;
if (info.perm_other.exec) mode |= S_IXOTH;
if (chmod(fp.data(), mode) != 0) {
piCout << "[PIFile] applyFileInfo: \"chmod\" error:" << errorString();
}
if (chown(fp.data(), info.id_user, info.id_group) != 0) {
piCout << "[PIFile] applyFileInfo: \"chown\" error:" << errorString();
}
struct timeval tm[2];
PISystemTime st = info.time_access.toSystemTime();
tm[0].tv_sec = st.seconds;
tm[0].tv_usec = st.nanoseconds / 1000;
st = info.time_modification.toSystemTime();
tm[1].tv_sec = st.seconds;
tm[1].tv_usec = st.nanoseconds / 1000;
if (utimes(fp.data(), tm) != 0) {
piCout << "[PIFile] applyFileInfo: \"utimes\" error:" << errorString();
}
#endif
return true;
}
PIByteArray PIFile::readAll(const PIString & path, bool forceRead) {
PIFile f(path, PIIODevice::ReadOnly);
if (!f.isOpened()) return PIByteArray();
return f.readAll(forceRead);
}
int PIFile::writeAll(const PIString & path, const PIByteArray & data) {
PIFile f(path, PIIODevice::ReadWrite);
if (!f.isOpened()) return 0;
f.clear();
return f.write(data.data(), data.size_s());
}