Files
pip/libs/main/io_devices/pifile.cpp
2025-09-04 17:52:31 +03:00

663 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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 <http://www.gnu.org/licenses/>.
*/
#include "pifile.h"
#include "pidir.h"
#include "piincludes_p.h"
#include "piiostream.h"
#include "piliterals_bytes.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 <fcntl.h>
# include <sys/stat.h>
# include <sys/time.h>
# include <utime.h>
#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<uchar>(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;
}
PIByteArray PIFile::_readAll() {
if (!isOpened()) return {};
llong prev_pos = pos();
PIByteArray ret, buffer(4_KiB);
seekToBegin();
for (;;) {
size_t readed = fread(buffer.data(), 1, buffer.size(), PRIVATE->fd);
if (readed == 0) break;
ret.append(buffer.data(), readed);
}
seek(prev_pos);
return ret;
}
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());
}