File indexing completed on 2025-01-05 04:37:30

0001 /*
0002     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "fileops.h"
0008 #include "config-ktorrent.h"
0009 
0010 #include <cstdlib>
0011 #include <errno.h>
0012 #include <fcntl.h>
0013 #include <string>
0014 #include <sys/stat.h>
0015 #include <sys/types.h>
0016 #include <unistd.h>
0017 
0018 #include <QDir>
0019 #include <QFile>
0020 #include <QSet>
0021 #include <QStringList>
0022 
0023 #include <KIO/CopyJob>
0024 #include <KIO/FileCopyJob>
0025 #include <KIO/Job>
0026 #include <KLocalizedString>
0027 #include <Solid/Device>
0028 #include <Solid/StorageAccess>
0029 #include <Solid/StorageDrive>
0030 #include <Solid/StorageVolume>
0031 
0032 #include "array.h"
0033 #include "error.h"
0034 #include "file.h"
0035 #include "functions.h"
0036 #include "log.h"
0037 #ifdef Q_WS_WIN
0038 #include "win32.h"
0039 #endif
0040 
0041 #include "limits.h"
0042 
0043 #ifndef NAME_MAX
0044 #define NAME_MAX 255
0045 #endif
0046 
0047 #ifndef PATH_MAX
0048 #define PATH_MAX 4096
0049 #endif
0050 
0051 #ifdef HAVE_XFS_XFS_H
0052 
0053 #if !defined(HAVE___S64) || !defined(HAVE___U64)
0054 #include <stdint.h>
0055 #endif
0056 
0057 #ifndef HAVE___U64
0058 typedef uint64_t __u64;
0059 #endif
0060 
0061 #ifndef HAVE___S64
0062 typedef int64_t __s64;
0063 #endif
0064 
0065 #include <xfs/xfs.h>
0066 #endif
0067 
0068 #ifndef O_LARGEFILE
0069 #define O_LARGEFILE 0
0070 #endif
0071 
0072 #ifndef Q_WS_WIN
0073 #ifdef Q_OS_LINUX
0074 #include <mntent.h>
0075 #endif
0076 #include <sys/statvfs.h>
0077 #endif
0078 #ifdef CopyFile
0079 #undef CopyFile
0080 #endif
0081 
0082 namespace bt
0083 {
0084 void MakeDir(const QString &dir, bool nothrow)
0085 {
0086     QDir d(dir);
0087     if (d.exists())
0088         return;
0089 
0090     QString n = d.dirName();
0091     if (!d.cdUp() || !d.mkdir(n)) {
0092         QString error = i18n("Cannot create directory %1", dir);
0093         Out(SYS_DIO | LOG_NOTICE) << error << endl;
0094         if (!nothrow)
0095             throw Error(error);
0096     }
0097 }
0098 
0099 void MakePath(const QString &dir, bool nothrow)
0100 {
0101     QStringList sl = dir.split(bt::DirSeparator(), Qt::SkipEmptyParts);
0102     QString ctmp;
0103 #ifndef Q_WS_WIN
0104     ctmp += bt::DirSeparator();
0105 #endif
0106 
0107     for (int i = 0; i < sl.count(); i++) {
0108         ctmp += sl[i];
0109         if (!bt::Exists(ctmp)) {
0110             try {
0111                 MakeDir(ctmp, false);
0112             } catch (...) {
0113                 if (!nothrow)
0114                     throw;
0115                 return;
0116             }
0117         }
0118 
0119         ctmp += bt::DirSeparator();
0120     }
0121 }
0122 
0123 void MakeFilePath(const QString &file, bool nothrow)
0124 {
0125     QStringList sl = file.split(bt::DirSeparator());
0126     QString ctmp;
0127 #ifndef Q_WS_WIN
0128     ctmp += bt::DirSeparator();
0129 #endif
0130 
0131     for (int i = 0; i < sl.count() - 1; i++) {
0132         ctmp += sl[i];
0133         if (!bt::Exists(ctmp))
0134             try {
0135                 MakeDir(ctmp, false);
0136             } catch (...) {
0137                 if (!nothrow)
0138                     throw;
0139                 return;
0140             }
0141 
0142         ctmp += bt::DirSeparator();
0143     }
0144 }
0145 
0146 void SymLink(const QString &link_to, const QString &link_url, bool nothrow)
0147 {
0148     if (symlink(QFile::encodeName(link_to).constData(), QFile::encodeName(link_url).constData()) != 0) {
0149         if (!nothrow)
0150             throw Error(i18n("Cannot symlink %1 to %2: %3", link_url, link_to, QString::fromUtf8(strerror(errno))));
0151         else
0152             Out(SYS_DIO | LOG_NOTICE)
0153                 << QStringLiteral("Error : Cannot symlink %1 to %2: %3").arg(link_url).arg(link_to).arg(QString::fromUtf8(strerror(errno))) << endl;
0154     }
0155 }
0156 
0157 void Move(const QString &src, const QString &dst, bool nothrow, bool silent)
0158 {
0159     //  Out() << "Moving " << src << " -> " << dst << endl;
0160     KIO::CopyJob *mv = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dst), silent ? KIO::HideProgressInfo | KIO::Overwrite : KIO::DefaultFlags);
0161     if (!mv->exec()) {
0162         if (!nothrow)
0163             throw Error(i18n("Cannot move %1 to %2: %3", src, dst, mv->errorString()));
0164         else
0165             Out(SYS_DIO | LOG_NOTICE) << QStringLiteral("Error : Cannot move %1 to %2: %3").arg(src).arg(dst).arg(mv->errorString()) << endl;
0166     }
0167 }
0168 
0169 void CopyFile(const QString &src, const QString &dst, bool nothrow)
0170 {
0171     KIO::FileCopyJob *copy = KIO::file_copy(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dst));
0172     if (!copy->exec()) {
0173         if (!nothrow)
0174             throw Error(i18n("Cannot copy %1 to %2: %3", src, dst, copy->errorString()));
0175         else
0176             Out(SYS_DIO | LOG_NOTICE) << QStringLiteral("Error : Cannot copy %1 to %2: %3").arg(src).arg(dst).arg(copy->errorString()) << endl;
0177     }
0178 }
0179 
0180 void CopyDir(const QString &src, const QString &dst, bool nothrow)
0181 {
0182     KIO::CopyJob *copy = KIO::copy(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dst));
0183     if (!copy->exec()) {
0184         if (!nothrow)
0185             throw Error(i18n("Cannot copy %1 to %2: %3", src, dst, copy->errorString()));
0186         else
0187             Out(SYS_DIO | LOG_NOTICE) << QStringLiteral("Error : Cannot copy %1 to %2: %3").arg(src).arg(dst).arg(copy->errorString()) << endl;
0188     }
0189 }
0190 
0191 bool Exists(const QString &url)
0192 {
0193     return QFile::exists(url);
0194 }
0195 
0196 static bool DelDir(const QString &fn)
0197 {
0198     QDir d(fn);
0199     const QStringList subdirs = d.entryList(QDir::Dirs);
0200 
0201     for (auto i = subdirs.begin(); i != subdirs.end(); i++) {
0202         QString entry = *i;
0203 
0204         if (entry == QLatin1String("..") || entry == QLatin1String("."))
0205             continue;
0206 
0207         if (!DelDir(d.absoluteFilePath(entry)))
0208             return false;
0209     }
0210 
0211     const QStringList files = d.entryList(QDir::Files | QDir::System | QDir::Hidden);
0212     for (auto i = files.begin(); i != files.end(); i++) {
0213         QString file = d.absoluteFilePath(*i);
0214         QFile fp(file);
0215         if (!QFileInfo(file).isWritable() && !fp.setPermissions(QFile::ReadUser | QFile::WriteUser))
0216             return false;
0217 
0218         if (!fp.remove())
0219             return false;
0220     }
0221 
0222     if (!d.rmdir(d.absolutePath()))
0223         return false;
0224 
0225     return true;
0226 }
0227 
0228 void Delete(const QString &url, bool nothrow)
0229 {
0230     bool ok = true;
0231     // first see if it is a directory
0232     if (QDir(url).exists()) {
0233         ok = DelDir(url);
0234     } else {
0235         ok = QFile::remove(url);
0236     }
0237 
0238     if (!ok) {
0239         QString err = i18n("Cannot delete %1: %2", url, QString::fromUtf8(strerror(errno)));
0240         if (!nothrow)
0241             throw Error(err);
0242         else
0243             Out(SYS_DIO | LOG_NOTICE) << "Error : " << err << endl;
0244     }
0245 }
0246 
0247 void Touch(const QString &url, bool nothrow)
0248 {
0249     if (Exists(url))
0250         return;
0251 
0252     File fptr;
0253     if (!fptr.open(url, QStringLiteral("wb"))) {
0254         if (!nothrow)
0255             throw Error(i18n("Cannot create %1: %2", url, fptr.errorString()));
0256         else
0257             Out(SYS_DIO | LOG_NOTICE) << "Error : Cannot create " << url << " : " << fptr.errorString() << endl;
0258     }
0259 }
0260 
0261 Uint64 FileSize(const QString &url)
0262 {
0263     int ret = 0;
0264 #ifdef HAVE_STAT64
0265     struct stat64 sb;
0266     ret = stat64(QFile::encodeName(url).constData(), &sb);
0267 #else
0268     struct stat sb;
0269     ret = stat(QFile::encodeName(url).constData(), &sb);
0270 #endif
0271     if (ret < 0)
0272         throw Error(i18n("Cannot calculate the filesize of %1: %2", url, QString::fromUtf8(strerror(errno))));
0273 
0274     return (Uint64)sb.st_size;
0275 }
0276 
0277 Uint64 FileSize(int fd)
0278 {
0279     int ret = 0;
0280 #ifdef HAVE_STAT64
0281     struct stat64 sb;
0282     ret = fstat64(fd, &sb);
0283 #else
0284     struct stat sb;
0285     ret = fstat(fd, &sb);
0286 #endif
0287     if (ret < 0)
0288         throw Error(i18n("Cannot calculate the filesize: %1", QString::fromUtf8(strerror(errno))));
0289 
0290     return (Uint64)sb.st_size;
0291 }
0292 
0293 #ifdef HAVE_XFS_XFS_H
0294 
0295 bool XfsPreallocate(int fd, Uint64 size)
0296 {
0297     if (!platform_test_xfs_fd(fd)) {
0298         return false;
0299     }
0300 
0301     xfs_flock64_t allocopt;
0302     allocopt.l_whence = 0;
0303     allocopt.l_start = 0;
0304     allocopt.l_len = size;
0305 
0306     return (!static_cast<bool>(xfsctl(0, fd, XFS_IOC_RESVSP64, &allocopt)));
0307 }
0308 
0309 bool XfsPreallocate(const QString &path, Uint64 size)
0310 {
0311     int fd = ::open(QFile::encodeName(path).constData(), O_RDWR | O_LARGEFILE);
0312     if (fd < 0)
0313         throw Error(i18n("Cannot open %1: %2", path, strerror(errno)));
0314 
0315     bool ret = XfsPreallocate(fd, size);
0316     close(fd);
0317     return ret;
0318 }
0319 
0320 #endif
0321 
0322 void TruncateFile(int fd, Uint64 size, bool quick)
0323 {
0324     if (FileSize(fd) == size)
0325         return;
0326 
0327     if (quick) {
0328 #ifdef HAVE_FTRUNCATE64
0329         if (ftruncate64(fd, size) == -1)
0330 #else
0331         if (ftruncate(fd, size) == -1)
0332 #endif
0333             throw Error(i18n("Cannot expand file: %1", QString::fromUtf8(strerror(errno))));
0334     } else {
0335 #ifdef HAVE_POSIX_FALLOCATE64
0336         if (posix_fallocate64(fd, 0, size) != 0)
0337             throw Error(i18n("Cannot expand file: %1", QString::fromUtf8(strerror(errno))));
0338 #elif HAVE_POSIX_FALLOCATE
0339         if (posix_fallocate(fd, 0, size) != 0)
0340             throw Error(i18n("Cannot expand file: %1", QString::fromUtf8(strerror(errno))));
0341 #elif HAVE_FTRUNCATE64
0342         if (ftruncate64(fd, size) == -1)
0343             throw Error(i18n("Cannot expand file: %1", QString::fromUtf8(strerror(errno))));
0344 #else
0345         if (ftruncate(fd, size) == -1)
0346             throw Error(i18n("Cannot expand file: %1", QString::fromUtf8(strerror(errno))));
0347 #endif
0348     }
0349 }
0350 
0351 void TruncateFile(const QString &path, Uint64 size)
0352 {
0353     int fd = ::open(QFile::encodeName(path).constData(), O_RDWR | O_LARGEFILE);
0354     if (fd < 0)
0355         throw Error(i18n("Cannot open %1: %2", path, QString::fromUtf8(strerror(errno))));
0356 
0357     try {
0358         TruncateFile(fd, size, true);
0359         close(fd);
0360     } catch (...) {
0361         close(fd);
0362         throw;
0363     }
0364 }
0365 
0366 void SeekFile(int fd, Int64 off, int whence)
0367 {
0368 #ifdef HAVE_LSEEK64
0369     if (lseek64(fd, off, whence) == -1)
0370 #else
0371     if (lseek(fd, off, whence) == -1)
0372 #endif
0373         throw Error(i18n("Cannot seek in file: %1", QString::fromUtf8(strerror(errno))));
0374 }
0375 
0376 bool FreeDiskSpace(const QString &path, Uint64 &bytes_free)
0377 {
0378 #ifdef HAVE_STATVFS
0379 #ifdef HAVE_STATVFS64
0380     struct statvfs64 stfs;
0381     if (statvfs64(QFile::encodeName(path).constData(), &stfs) == 0)
0382 #else
0383     struct statvfs stfs;
0384     if (statvfs(QFile::encodeName(path).constData(), &stfs) == 0)
0385 #endif
0386     {
0387         if (stfs.f_blocks == 0) // if this is 0, then we are using gvfs
0388             return false;
0389 
0390         bytes_free = ((Uint64)stfs.f_bavail) * ((Uint64)stfs.f_frsize);
0391         return true;
0392     } else {
0393         Out(SYS_GEN | LOG_DEBUG) << "Error : statvfs for " << path << " failed :  " << QString::fromUtf8(strerror(errno)) << endl;
0394 
0395         return false;
0396     }
0397 #elif defined(Q_WS_WIN)
0398 #ifdef UNICODE
0399     LPCWSTR tpath = (LPCWSTR)path.utf16();
0400 #else
0401     const char *tpath = path.toLocal8Bit();
0402 #endif
0403     if (GetDiskFreeSpaceEx(tpath, (PULARGE_INTEGER)&bytes_free, NULL, NULL)) {
0404         return true;
0405     } else {
0406         return false;
0407     }
0408 #else
0409     return false;
0410 #endif
0411 }
0412 
0413 bool FileNameToLong(const QString &path)
0414 {
0415     int length = 0;
0416     const QStringList names = path.split(QLatin1Char('/'));
0417     for (const QString &s : names) {
0418         QByteArray encoded = QFile::encodeName(s);
0419         if (encoded.length() >= NAME_MAX)
0420             return true;
0421         length += encoded.length();
0422     }
0423 
0424     length += path.count(QLatin1Char('/'));
0425     return length >= PATH_MAX;
0426 }
0427 
0428 static QString ShortenName(const QString &name, int extra_number)
0429 {
0430     QFileInfo fi(name);
0431     QString ext = fi.suffix();
0432     QString base = fi.completeBaseName();
0433 
0434     // calculate the fixed length, 1 is for the . between filename and extension
0435     int fixed_len = 0;
0436     if (ext.length() > 0)
0437         fixed_len += QFile::encodeName(ext).length() + 1;
0438     if (extra_number > 0)
0439         fixed_len += QFile::encodeName(QString::number(extra_number)).length();
0440 
0441     // if we can't shorten it, give up
0442     if (fixed_len > NAME_MAX - 4)
0443         return name;
0444 
0445     do {
0446         base.chop(1);
0447     } while (fixed_len + QFile::encodeName(base).length() > NAME_MAX - 4 && base.length() != 0);
0448 
0449     base += QStringLiteral("... "); // add ... so that the user knows the name is shortened
0450 
0451     QString ret = base;
0452     if (extra_number > 0)
0453         ret += QString::number(extra_number);
0454     if (ext.length() > 0)
0455         ret += QLatin1Char('.') + ext;
0456     return ret;
0457 }
0458 
0459 static QString ShortenPath(const QString &path, int extra_number)
0460 {
0461     int max_len = PATH_MAX;
0462     QByteArray encoded = QFile::encodeName(path);
0463     if (encoded.length() < max_len)
0464         return path;
0465 
0466     QFileInfo fi(path);
0467     QString ext = fi.suffix();
0468     QString name = fi.completeBaseName();
0469     QString fpath = fi.path() + '/';
0470 
0471     // calculate the fixed length, 1 is for the . between filename and extension
0472     int fixed_len = QFile::encodeName(fpath).length();
0473     if (ext.length() > 0)
0474         fixed_len += QFile::encodeName(ext).length() + 1;
0475     if (extra_number > 0)
0476         fixed_len += QFile::encodeName(QString::number(extra_number)).length();
0477 
0478     // if we can't shorten it, give up
0479     if (fixed_len > max_len - 4)
0480         return path;
0481 
0482     do {
0483         name.chop(1);
0484     } while (fixed_len + QFile::encodeName(name).length() > max_len - 4 && name.length() != 0);
0485 
0486     name += QLatin1String("... "); // add ... so that the user knows the name is shortened
0487 
0488     QString ret = fpath + name;
0489     if (extra_number > 0)
0490         ret += QString::number(extra_number);
0491     if (ext.length() > 0)
0492         ret += QLatin1Char('.') + ext;
0493 
0494     return ret;
0495 }
0496 
0497 QString ShortenFileName(const QString &path, int extra_number)
0498 {
0499     QString assembled = QStringLiteral("/");
0500     const QStringList names = path.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0501     int cnt = 0;
0502     for (const QString &s : names) {
0503         QByteArray encoded = QFile::encodeName(s);
0504         assembled += (encoded.length() < NAME_MAX) ? s : ShortenName(s, extra_number);
0505         if (cnt < names.count() - 1)
0506             assembled += QLatin1Char('/');
0507         cnt++;
0508     }
0509 
0510     if (QFile::encodeName(assembled).length() >= PATH_MAX) {
0511         // still to long, then the Shorten the filename
0512         assembled = ShortenPath(assembled, extra_number);
0513     }
0514 
0515     return assembled;
0516 }
0517 
0518 Uint64 DiskUsage(const QString &filename)
0519 {
0520     Uint64 ret = 0;
0521 #ifndef Q_WS_WIN
0522 #ifdef HAVE_STAT64
0523     struct stat64 sb;
0524     if (stat64(QFile::encodeName(filename).constData(), &sb) == 0)
0525 #else
0526     struct stat sb;
0527     if (stat(QFile::encodeName(filename).constData(), &sb) == 0)
0528 #endif
0529     {
0530         ret = (Uint64)sb.st_blocks * 512;
0531     }
0532 #else
0533     DWORD high = 0;
0534     DWORD low = GetCompressedFileSize((LPWSTR)filename.utf16(), &high);
0535     if (low != INVALID_FILE_SIZE)
0536         ret = (high * MAXDWORD) + low;
0537 #endif
0538     return ret;
0539 }
0540 
0541 Uint64 DiskUsage(int fd)
0542 {
0543     Uint64 ret = 0;
0544 #ifndef Q_WS_WIN
0545 #ifdef HAVE_FSTAT64
0546     struct stat64 sb;
0547     if (fstat64(fd, &sb) == 0)
0548 #else
0549     struct stat sb;
0550     if (fstat(fd, &sb) == 0)
0551 #endif
0552     {
0553         ret = (Uint64)sb.st_blocks * 512;
0554     }
0555 #else
0556     struct _BY_HANDLE_FILE_INFORMATION info;
0557     GetFileInformationByHandle((void *)&fd, &info);
0558     ret = (info.nFileSizeHigh * MAXDWORD) + info.nFileSizeLow;
0559 #endif
0560     return ret;
0561 }
0562 
0563 QSet<QString> AccessibleMountPoints()
0564 {
0565     QSet<QString> result;
0566 #ifdef Q_OS_LINUX
0567     FILE *fptr = setmntent("/proc/mounts", "r");
0568     if (!fptr)
0569         return result;
0570 
0571     struct mntent mnt;
0572     char buf[PATH_MAX];
0573     while (getmntent_r(fptr, &mnt, buf, PATH_MAX)) {
0574         result.insert(QString::fromUtf8(mnt.mnt_dir));
0575     }
0576 
0577     endmntent(fptr);
0578 
0579 #else
0580     const QList<Solid::Device> devs = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess);
0581     for (const Solid::Device &dev : devs) {
0582         const Solid::StorageAccess *sa = dev.as<Solid::StorageAccess>();
0583         if (!sa->filePath().isEmpty() && sa->isAccessible())
0584             result.insert(sa->filePath());
0585     }
0586 #endif
0587     return result;
0588 }
0589 
0590 QString MountPoint(const QString &path)
0591 {
0592     const QSet<QString> mount_points = AccessibleMountPoints();
0593     QString mount_point;
0594     for (const QString &mp : mount_points) {
0595         if (path.startsWith(mp) && (mount_point.isEmpty() || mp.startsWith(mount_point))) {
0596             mount_point = mp;
0597         }
0598     }
0599 
0600     return mount_point;
0601 }
0602 
0603 bool IsMounted(const QString &mount_point)
0604 {
0605     return AccessibleMountPoints().contains(mount_point);
0606 }
0607 
0608 QByteArray LoadFile(const QString &path)
0609 {
0610     QFile fptr(path);
0611     if (fptr.open(QIODevice::ReadOnly))
0612         return fptr.readAll();
0613     else
0614         throw Error(i18n("Unable to open file %1: %2", path, fptr.errorString()));
0615 }
0616 
0617 }