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 }