/* 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 "piincludes_p.h" #include "pifile.h" #include "pidir.h" #include "piiostream.h" #include "pitime_win.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; } 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()); #else char * rc = tmpnam(0); if (!rc) return false; tp = rc; #endif while (isExists(tp)) { tp += PIString::fromNumber(randomi() % 10); } 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(); while (!isEnd()) a.push_back(readChar()); seek(cp); return a; } llong s = size(); if (s < 0) return a; a.resize(s); seekToBegin(); fread(a.data(), 1, s, PRIVATE->fd); 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 :-("; } 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; memset(&fi, 0, sizeof(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; memset(&fs, 0, sizeof(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); memset(&fs, 0, sizeof(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; }