| 1 | /* |
| 2 | SPDX-FileCopyrightText: 2000-2002 Stephan Kulow <coolo@kde.org> |
| 3 | SPDX-FileCopyrightText: 2000-2002 David Faure <faure@kde.org> |
| 4 | SPDX-FileCopyrightText: 2000-2002 Waldo Bastian <bastian@kde.org> |
| 5 | SPDX-FileCopyrightText: 2006 Allan Sandfeld Jensen <sandfeld@kde.org> |
| 6 | SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org> |
| 7 | SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> |
| 8 | |
| 9 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 10 | */ |
| 11 | |
| 12 | #include "file.h" |
| 13 | |
| 14 | #include <QDirIterator> |
| 15 | |
| 16 | #include <QStorageInfo> |
| 17 | |
| 18 | #include "../../utils_p.h" |
| 19 | #include "kioglobal_p.h" |
| 20 | #include "statjob.h" |
| 21 | |
| 22 | #include <assert.h> |
| 23 | #include <cerrno> |
| 24 | #ifdef Q_OS_WIN |
| 25 | #include <qt_windows.h> |
| 26 | #include <sys/utime.h> |
| 27 | #include <winsock2.h> //struct timeval |
| 28 | #else |
| 29 | #include <utime.h> |
| 30 | #endif |
| 31 | |
| 32 | #include <QCoreApplication> |
| 33 | #include <QDate> |
| 34 | #include <QTemporaryFile> |
| 35 | #include <QVarLengthArray> |
| 36 | #ifdef Q_OS_WIN |
| 37 | #include <QDir> |
| 38 | #include <QFileInfo> |
| 39 | #endif |
| 40 | |
| 41 | #include <KConfigGroup> |
| 42 | #include <KLocalizedString> |
| 43 | #include <KShell> |
| 44 | #include <QDataStream> |
| 45 | #include <QDebug> |
| 46 | #include <QMimeDatabase> |
| 47 | #include <QStandardPaths> |
| 48 | #include <kmountpoint.h> |
| 49 | |
| 50 | #include <ioworker_defaults.h> |
| 51 | #include <kdirnotify.h> |
| 52 | #include <workerfactory.h> |
| 53 | |
| 54 | Q_LOGGING_CATEGORY(KIO_FILE, "kf.kio.workers.file" ) |
| 55 | |
| 56 | class KIOPluginFactory : public KIO::WorkerFactory |
| 57 | { |
| 58 | Q_OBJECT |
| 59 | Q_PLUGIN_METADATA(IID "org.kde.kio.worker.file" FILE "file.json" ) |
| 60 | |
| 61 | public: |
| 62 | std::unique_ptr<KIO::WorkerBase> createWorker(const QByteArray &pool, const QByteArray &app) override |
| 63 | { |
| 64 | return std::make_unique<FileProtocol>(args: pool, args: app); |
| 65 | } |
| 66 | }; |
| 67 | |
| 68 | using namespace KIO; |
| 69 | |
| 70 | static constexpr int s_maxIPCSize = 1024 * 32; |
| 71 | |
| 72 | static QString readLogFile(const QByteArray &_filename); |
| 73 | |
| 74 | extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) |
| 75 | { |
| 76 | QCoreApplication app(argc, argv); // needed for QSocketNotifier |
| 77 | app.setApplicationName(QStringLiteral("kio_file" )); |
| 78 | |
| 79 | if (argc != 4) { |
| 80 | fprintf(stderr, format: "Usage: kio_file protocol domain-socket1 domain-socket2\n" ); |
| 81 | exit(status: -1); |
| 82 | } |
| 83 | |
| 84 | FileProtocol worker(argv[2], argv[3]); |
| 85 | |
| 86 | // Make sure the first qDebug is after the worker ctor (which sets a SIGPIPE handler) |
| 87 | // This is useful in case kdeinit was autostarted by another app, which then exited and closed fd2 |
| 88 | // (e.g. ctest does that, or closing the terminal window would do that) |
| 89 | // qDebug() << "Starting" << getpid(); |
| 90 | |
| 91 | worker.dispatchLoop(); |
| 92 | |
| 93 | // qDebug() << "Done"; |
| 94 | return 0; |
| 95 | } |
| 96 | |
| 97 | static QFile::Permissions modeToQFilePermissions(int mode) |
| 98 | { |
| 99 | QFile::Permissions perms; |
| 100 | if (mode & S_IRUSR) { |
| 101 | perms |= QFile::ReadOwner; |
| 102 | } |
| 103 | if (mode & S_IWUSR) { |
| 104 | perms |= QFile::WriteOwner; |
| 105 | } |
| 106 | if (mode & S_IXUSR) { |
| 107 | perms |= QFile::ExeOwner; |
| 108 | } |
| 109 | if (mode & S_IRGRP) { |
| 110 | perms |= QFile::ReadGroup; |
| 111 | } |
| 112 | if (mode & S_IWGRP) { |
| 113 | perms |= QFile::WriteGroup; |
| 114 | } |
| 115 | if (mode & S_IXGRP) { |
| 116 | perms |= QFile::ExeGroup; |
| 117 | } |
| 118 | if (mode & S_IROTH) { |
| 119 | perms |= QFile::ReadOther; |
| 120 | } |
| 121 | if (mode & S_IWOTH) { |
| 122 | perms |= QFile::WriteOther; |
| 123 | } |
| 124 | if (mode & S_IXOTH) { |
| 125 | perms |= QFile::ExeOther; |
| 126 | } |
| 127 | |
| 128 | return perms; |
| 129 | } |
| 130 | |
| 131 | FileProtocol::FileProtocol(const QByteArray &pool, const QByteArray &app) |
| 132 | : KIO::WorkerBase(QByteArrayLiteral("file" ), pool, app) |
| 133 | , mFile(nullptr) |
| 134 | { |
| 135 | testMode = !qEnvironmentVariableIsEmpty(varName: "KIOWORKER_FILE_ENABLE_TESTMODE" ); |
| 136 | } |
| 137 | |
| 138 | FileProtocol::~FileProtocol() |
| 139 | { |
| 140 | } |
| 141 | |
| 142 | WorkerResult FileProtocol::chmod(const QUrl &url, int permissions) |
| 143 | { |
| 144 | const QString path(url.toLocalFile()); |
| 145 | const QByteArray _path(QFile::encodeName(fileName: path)); |
| 146 | /* FIXME: Should be atomic */ |
| 147 | #ifdef Q_OS_UNIX |
| 148 | // QFile::Permissions does not support special attributes like sticky |
| 149 | if (::chmod(file: _path.constData(), mode: permissions) == -1 || |
| 150 | #else |
| 151 | if (!QFile::setPermissions(path, modeToQFilePermissions(permissions)) || |
| 152 | #endif |
| 153 | (setACL(path: _path.data(), perm: permissions, directoryDefault: false) == -1) || |
| 154 | /* if not a directory, cannot set default ACLs */ |
| 155 | (setACL(path: _path.data(), perm: permissions, directoryDefault: true) == -1 && errno != ENOTDIR)) { |
| 156 | auto result = execWithElevatedPrivilege(action: CHMOD, args: {_path, permissions}, errno); |
| 157 | if (!result.success()) { |
| 158 | if (!resultWasCancelled(result)) { |
| 159 | switch (result.error()) { |
| 160 | case EPERM: |
| 161 | case EACCES: |
| 162 | return WorkerResult::fail(error: KIO::ERR_ACCESS_DENIED, errorString: path); |
| 163 | break; |
| 164 | #if defined(ENOTSUP) |
| 165 | case ENOTSUP: // from setACL since chmod can't return ENOTSUP |
| 166 | return WorkerResult::fail(error: KIO::ERR_UNSUPPORTED_ACTION, i18n("Setting ACL for %1" , path)); |
| 167 | break; |
| 168 | #endif |
| 169 | case ENOSPC: |
| 170 | return WorkerResult::fail(error: KIO::ERR_DISK_FULL, errorString: path); |
| 171 | break; |
| 172 | default: |
| 173 | return WorkerResult::fail(error: KIO::ERR_CANNOT_CHMOD, errorString: path); |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | return WorkerResult::pass(); |
| 180 | } |
| 181 | |
| 182 | WorkerResult FileProtocol::setModificationTime(const QUrl &url, const QDateTime &mtime) |
| 183 | { |
| 184 | const QString path(url.toLocalFile()); |
| 185 | QT_STATBUF statbuf; |
| 186 | if (QT_LSTAT(file: QFile::encodeName(fileName: path).constData(), buf: &statbuf) == 0) { |
| 187 | struct utimbuf utbuf; |
| 188 | utbuf.actime = statbuf.st_atime; // access time, unchanged |
| 189 | utbuf.modtime = mtime.toSecsSinceEpoch(); // modification time |
| 190 | if (::utime(file: QFile::encodeName(fileName: path).constData(), file_times: &utbuf) != 0) { |
| 191 | auto result = execWithElevatedPrivilege(action: UTIME, args: {path, qint64(utbuf.actime), qint64(utbuf.modtime)}, errno); |
| 192 | if (!result.success()) { |
| 193 | if (!resultWasCancelled(result)) { |
| 194 | // TODO: errno could be EACCES, EPERM, EROFS |
| 195 | return WorkerResult::fail(error: KIO::ERR_CANNOT_SETTIME, errorString: path); |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | return WorkerResult::pass(); |
| 200 | } else { |
| 201 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: path); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | WorkerResult FileProtocol::mkdir(const QUrl &url, int permissions) |
| 206 | { |
| 207 | const QString path(url.toLocalFile()); |
| 208 | |
| 209 | // qDebug() << path << "permission=" << permissions; |
| 210 | |
| 211 | // Remove existing file or symlink, if requested (#151851) |
| 212 | if (metaData(QStringLiteral("overwrite" )) == QLatin1String("true" )) { |
| 213 | if (!QFile::remove(fileName: path)) { |
| 214 | execWithElevatedPrivilege(action: DEL, args: {path}, errno); |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | QT_STATBUF buff; |
| 219 | if (QT_LSTAT(file: QFile::encodeName(fileName: path).constData(), buf: &buff) == -1) { |
| 220 | bool dirCreated = QDir().mkdir(dirName: path); |
| 221 | if (!dirCreated) { |
| 222 | auto result = execWithElevatedPrivilege(action: MKDIR, args: {path}, errno); |
| 223 | if (!result.success()) { |
| 224 | if (!resultWasCancelled(result)) { |
| 225 | // TODO: add access denied & disk full (or another reasons) handling (into Qt, possibly) |
| 226 | return WorkerResult::fail(error: KIO::ERR_CANNOT_MKDIR, errorString: path); |
| 227 | } |
| 228 | return WorkerResult::pass(); |
| 229 | } |
| 230 | dirCreated = true; |
| 231 | } |
| 232 | |
| 233 | if (dirCreated) { |
| 234 | if (permissions != -1) { |
| 235 | return chmod(url, permissions); |
| 236 | } |
| 237 | return WorkerResult::pass(); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | if (Utils::isDirMask(mode: buff.st_mode)) { |
| 242 | // qDebug() << "ERR_DIR_ALREADY_EXIST"; |
| 243 | return WorkerResult::fail(error: KIO::ERR_DIR_ALREADY_EXIST, errorString: path); |
| 244 | } |
| 245 | return WorkerResult::fail(error: KIO::ERR_FILE_ALREADY_EXIST, errorString: path); |
| 246 | } |
| 247 | |
| 248 | WorkerResult FileProtocol::redirect(const QUrl &url) |
| 249 | { |
| 250 | QUrl redir(url); |
| 251 | redir.setScheme(configValue(QStringLiteral("DefaultRemoteProtocol" ), QStringLiteral("smb" ))); |
| 252 | |
| 253 | // if we would redirect into the Windows world, let's also check for the |
| 254 | // DavWWWRoot "token" which in the Windows world tells win explorer to access |
| 255 | // a webdav url |
| 256 | // https://www.webdavsystem.com/server/access/windows |
| 257 | const QLatin1String davRoot("/DavWWWRoot/" ); |
| 258 | if ((redir.scheme() == QLatin1String("smb" )) && redir.path().startsWith(s: davRoot)) { |
| 259 | redir.setPath(path: redir.path().mid(position: davRoot.size() - 1)); // remove /DavWWWRoot |
| 260 | redir.setScheme(QStringLiteral("webdav" )); |
| 261 | } |
| 262 | |
| 263 | redirection(url: redir); |
| 264 | return WorkerResult::pass(); |
| 265 | } |
| 266 | |
| 267 | WorkerResult FileProtocol::get(const QUrl &url) |
| 268 | { |
| 269 | if (!url.isLocalFile()) { |
| 270 | return redirect(url); |
| 271 | } |
| 272 | |
| 273 | const QString path(url.toLocalFile()); |
| 274 | QT_STATBUF buff; |
| 275 | if (QT_STAT(file: QFile::encodeName(fileName: path).constData(), buf: &buff) == -1) { |
| 276 | if (errno == EACCES) { |
| 277 | return WorkerResult::fail(error: KIO::ERR_ACCESS_DENIED, errorString: path); |
| 278 | } else { |
| 279 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: path); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | if (Utils::isDirMask(mode: buff.st_mode)) { |
| 284 | return WorkerResult::fail(error: KIO::ERR_IS_DIRECTORY, errorString: path); |
| 285 | } |
| 286 | if (!Utils::isRegFileMask(mode: buff.st_mode)) { |
| 287 | return WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_READING, errorString: path); |
| 288 | } |
| 289 | |
| 290 | QFile f(path); |
| 291 | if (!f.open(flags: QIODevice::ReadOnly)) { |
| 292 | auto result = tryOpen(f, path: QFile::encodeName(fileName: path), O_RDONLY, S_IRUSR, errno); |
| 293 | if (!result.success()) { |
| 294 | if (!resultWasCancelled(result)) { |
| 295 | return WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_READING, errorString: path); |
| 296 | } |
| 297 | return WorkerResult::pass(); |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | #if HAVE_FADVISE |
| 302 | // TODO check return code |
| 303 | posix_fadvise(fd: f.handle(), offset: 0, len: 0, POSIX_FADV_SEQUENTIAL); |
| 304 | #endif |
| 305 | |
| 306 | // Determine the MIME type of the file to be retrieved, and emit it. |
| 307 | // This is mandatory in all workers (for KRun/BrowserRun to work) |
| 308 | // In real "remote" workers, this is usually done using mimeTypeForFileNameAndData |
| 309 | // after receiving some data. But we don't know how much data the mimemagic rules |
| 310 | // need, so for local files, better use mimeTypeForFile. |
| 311 | QMimeDatabase db; |
| 312 | QMimeType mt = db.mimeTypeForFile(fileName: url.toLocalFile()); |
| 313 | mimeType(type: mt.name()); |
| 314 | // Emit total size AFTER the MIME type |
| 315 | totalSize(bytes: buff.st_size); |
| 316 | |
| 317 | KIO::filesize_t processed_size = 0; |
| 318 | |
| 319 | QString resumeOffset = metaData(QStringLiteral("range-start" )); |
| 320 | if (resumeOffset.isEmpty()) { |
| 321 | resumeOffset = metaData(QStringLiteral("resume" )); // old name |
| 322 | } |
| 323 | if (!resumeOffset.isEmpty()) { |
| 324 | bool ok; |
| 325 | KIO::fileoffset_t offset = resumeOffset.toLongLong(ok: &ok); |
| 326 | if (ok && (offset > 0) && (offset < buff.st_size)) { |
| 327 | if (f.seek(offset)) { |
| 328 | canResume(); |
| 329 | processed_size = offset; |
| 330 | // qDebug() << "Resume offset:" << KIO::number(offset); |
| 331 | } |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | char buffer[s_maxIPCSize]; |
| 336 | QByteArray array; |
| 337 | |
| 338 | while (1) { |
| 339 | if (wasKilled()) { |
| 340 | return WorkerResult::pass(); |
| 341 | } |
| 342 | int n = f.read(data: buffer, maxlen: s_maxIPCSize); |
| 343 | if (n == -1) { |
| 344 | if (errno == EINTR) { |
| 345 | continue; |
| 346 | } |
| 347 | f.close(); |
| 348 | return WorkerResult::fail(error: ERR_CANNOT_READ, errorString: path); |
| 349 | } |
| 350 | if (n == 0) { |
| 351 | break; // Finished |
| 352 | } |
| 353 | |
| 354 | array = QByteArray::fromRawData(data: buffer, size: n); |
| 355 | data(data: array); |
| 356 | array.clear(); |
| 357 | |
| 358 | processed_size += n; |
| 359 | processedSize(bytes: processed_size); |
| 360 | |
| 361 | // qDebug() << "Processed: " << KIO::number (processed_size); |
| 362 | } |
| 363 | |
| 364 | data(data: QByteArray()); |
| 365 | |
| 366 | f.close(); |
| 367 | |
| 368 | processedSize(bytes: buff.st_size); |
| 369 | return WorkerResult::pass(); |
| 370 | } |
| 371 | |
| 372 | KIO::StatDetails FileProtocol::getStatDetails() |
| 373 | { |
| 374 | const QString statDetails = metaData(QStringLiteral("details" )); |
| 375 | return statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt()); |
| 376 | } |
| 377 | |
| 378 | WorkerResult FileProtocol::open(const QUrl &url, QIODevice::OpenMode mode) |
| 379 | { |
| 380 | // qDebug() << url; |
| 381 | |
| 382 | QString openPath = url.toLocalFile(); |
| 383 | QT_STATBUF buff; |
| 384 | if (QT_STAT(file: QFile::encodeName(fileName: openPath).constData(), buf: &buff) == -1) { |
| 385 | if (errno == EACCES) { |
| 386 | return WorkerResult::fail(error: KIO::ERR_ACCESS_DENIED, errorString: openPath); |
| 387 | } else { |
| 388 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: openPath); |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | if (Utils::isDirMask(mode: buff.st_mode)) { |
| 393 | return WorkerResult::fail(error: KIO::ERR_IS_DIRECTORY, errorString: openPath); |
| 394 | } |
| 395 | if (!Utils::isRegFileMask(mode: buff.st_mode)) { |
| 396 | return WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_READING, errorString: openPath); |
| 397 | } |
| 398 | |
| 399 | mFile = new QFile(openPath); |
| 400 | if (!mFile->open(flags: mode)) { |
| 401 | if (mode & QIODevice::ReadOnly) { |
| 402 | return WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_READING, errorString: openPath); |
| 403 | } else { |
| 404 | return WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_WRITING, errorString: openPath); |
| 405 | } |
| 406 | } |
| 407 | // Determine the MIME type of the file to be retrieved, and emit it. |
| 408 | // This is mandatory in all workers (for KRun/BrowserRun to work). |
| 409 | // If we're not opening the file ReadOnly or ReadWrite, don't attempt to |
| 410 | // read the file and send the MIME type. |
| 411 | if (mode & QIODevice::ReadOnly) { |
| 412 | QMimeDatabase db; |
| 413 | QMimeType mt = db.mimeTypeForFile(fileName: url.toLocalFile()); |
| 414 | mimeType(type: mt.name()); |
| 415 | } |
| 416 | |
| 417 | totalSize(bytes: buff.st_size); |
| 418 | position(pos: 0); |
| 419 | |
| 420 | return WorkerResult::pass(); |
| 421 | } |
| 422 | |
| 423 | WorkerResult FileProtocol::read(KIO::filesize_t bytes) |
| 424 | { |
| 425 | // qDebug() << "File::open -- read"; |
| 426 | Q_ASSERT(mFile && mFile->isOpen()); |
| 427 | |
| 428 | QVarLengthArray<char> buffer(bytes); |
| 429 | |
| 430 | qint64 bytesRead = mFile->read(data: buffer.data(), maxlen: bytes); |
| 431 | |
| 432 | if (bytesRead == -1) { |
| 433 | const auto fileName = mFile->fileName(); |
| 434 | qCWarning(KIO_FILE) << "Couldn't read. Error:" << mFile->errorString(); |
| 435 | closeWithoutFinish(); |
| 436 | return WorkerResult::fail(error: KIO::ERR_CANNOT_READ, errorString: fileName); |
| 437 | } else { |
| 438 | const QByteArray fileData = QByteArray::fromRawData(data: buffer.data(), size: bytesRead); |
| 439 | data(data: fileData); |
| 440 | return WorkerResult::pass(); |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | WorkerResult FileProtocol::write(const QByteArray &data) |
| 445 | { |
| 446 | // qDebug() << "File::open -- write"; |
| 447 | Q_ASSERT(mFile && mFile->isWritable()); |
| 448 | |
| 449 | qint64 bytesWritten = mFile->write(data); |
| 450 | |
| 451 | if (bytesWritten == -1) { |
| 452 | if (mFile->error() == QFileDevice::ResourceError) { // disk full |
| 453 | const auto fileName = mFile->fileName(); |
| 454 | closeWithoutFinish(); |
| 455 | return WorkerResult::fail(error: KIO::ERR_DISK_FULL, errorString: fileName); |
| 456 | } else { |
| 457 | const auto fileName = mFile->fileName(); |
| 458 | qCWarning(KIO_FILE) << "Couldn't write. Error:" << mFile->errorString(); |
| 459 | closeWithoutFinish(); |
| 460 | return WorkerResult::fail(error: KIO::ERR_CANNOT_WRITE, errorString: fileName); |
| 461 | } |
| 462 | } else { |
| 463 | mFile->flush(); |
| 464 | written(bytes: bytesWritten); |
| 465 | |
| 466 | return WorkerResult::pass(); |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | KIO::WorkerResult FileProtocol::seek(KIO::filesize_t offset) |
| 471 | { |
| 472 | // qDebug() << "File::open -- seek"; |
| 473 | Q_ASSERT(mFile && mFile->isOpen()); |
| 474 | |
| 475 | if (mFile->seek(offset)) { |
| 476 | position(pos: offset); |
| 477 | return WorkerResult::pass(); |
| 478 | } else { |
| 479 | const auto fileName = mFile->fileName(); |
| 480 | closeWithoutFinish(); |
| 481 | return WorkerResult::fail(error: KIO::ERR_CANNOT_SEEK, errorString: fileName); |
| 482 | } |
| 483 | } |
| 484 | |
| 485 | KIO::WorkerResult FileProtocol::truncate(KIO::filesize_t length) |
| 486 | { |
| 487 | Q_ASSERT(mFile && mFile->isOpen()); |
| 488 | |
| 489 | if (mFile->resize(sz: length)) { |
| 490 | truncated(length: length); |
| 491 | return WorkerResult::pass(); |
| 492 | } else { |
| 493 | const auto fileName = mFile->fileName(); |
| 494 | closeWithoutFinish(); |
| 495 | return WorkerResult::fail(error: KIO::ERR_CANNOT_TRUNCATE, errorString: fileName); |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | void FileProtocol::closeWithoutFinish() |
| 500 | { |
| 501 | Q_ASSERT(mFile); |
| 502 | |
| 503 | delete mFile; |
| 504 | mFile = nullptr; |
| 505 | } |
| 506 | |
| 507 | bool FileProtocol::resultWasCancelled(KIO::WorkerResult result) |
| 508 | { |
| 509 | int err = result.error(); |
| 510 | return err == KIO::ERR_USER_CANCELED || err == KIO::ERR_PRIVILEGE_NOT_REQUIRED; |
| 511 | } |
| 512 | |
| 513 | KIO::WorkerResult FileProtocol::close() |
| 514 | { |
| 515 | // qDebug() << "File::open -- close "; |
| 516 | closeWithoutFinish(); |
| 517 | return WorkerResult::pass(); |
| 518 | } |
| 519 | |
| 520 | KIO::WorkerResult FileProtocol::put(const QUrl &url, int _mode, KIO::JobFlags _flags) |
| 521 | { |
| 522 | if (privilegeOperationUnitTestMode()) { |
| 523 | return WorkerResult::pass(); |
| 524 | } |
| 525 | |
| 526 | const QString dest_orig = url.toLocalFile(); |
| 527 | |
| 528 | // qDebug() << dest_orig << "mode=" << _mode; |
| 529 | |
| 530 | QString dest_part(dest_orig + QLatin1String(".part" )); |
| 531 | |
| 532 | QT_STATBUF buff_orig; |
| 533 | const bool bOrigExists = (QT_LSTAT(file: QFile::encodeName(fileName: dest_orig).constData(), buf: &buff_orig) != -1); |
| 534 | bool bPartExists = false; |
| 535 | const bool bMarkPartial = configValue(QStringLiteral("MarkPartial" ), defaultValue: true); |
| 536 | |
| 537 | if (bMarkPartial) { |
| 538 | QT_STATBUF buff_part; |
| 539 | bPartExists = (QT_LSTAT(file: QFile::encodeName(fileName: dest_part).constData(), buf: &buff_part) != -1); |
| 540 | |
| 541 | if (bPartExists // |
| 542 | && !(_flags & KIO::Resume) // |
| 543 | && !(_flags & KIO::Overwrite) // |
| 544 | && buff_part.st_size > 0 // |
| 545 | && Utils::isRegFileMask(mode: buff_part.st_mode) // |
| 546 | ) { |
| 547 | // qDebug() << "calling canResume with" << KIO::number(buff_part.st_size); |
| 548 | |
| 549 | // Maybe we can use this partial file for resuming |
| 550 | // Tell about the size we have, and the app will tell us |
| 551 | // if it's ok to resume or not. |
| 552 | _flags |= canResume(offset: buff_part.st_size) ? KIO::Resume : KIO::DefaultFlags; |
| 553 | |
| 554 | // qDebug() << "got answer" << (_flags & KIO::Resume); |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | if (bOrigExists && !(_flags & KIO::Overwrite) && !(_flags & KIO::Resume)) { |
| 559 | if (Utils::isDirMask(mode: buff_orig.st_mode)) { |
| 560 | return WorkerResult::fail(error: KIO::ERR_DIR_ALREADY_EXIST, errorString: dest_orig); |
| 561 | } else { |
| 562 | return WorkerResult::fail(error: KIO::ERR_FILE_ALREADY_EXIST, errorString: dest_orig); |
| 563 | } |
| 564 | return WorkerResult::pass(); |
| 565 | } |
| 566 | |
| 567 | // Don't change permissions of the original file |
| 568 | if (bOrigExists && _mode == -1) { |
| 569 | _mode = static_cast<int>(buff_orig.st_mode); |
| 570 | // Make sure the value fit by casting it back. mode_t is possibly larger than int |
| 571 | Q_ASSERT(static_cast<decltype(buff_orig.st_mode)>(_mode) == buff_orig.st_mode); |
| 572 | } |
| 573 | #if !defined(Q_OS_WIN) |
| 574 | uid_t owner = -1; |
| 575 | gid_t group = -1; |
| 576 | if (bOrigExists) { |
| 577 | owner = buff_orig.st_uid; |
| 578 | group = buff_orig.st_gid; |
| 579 | } |
| 580 | #endif |
| 581 | |
| 582 | int result; |
| 583 | int error = 0; |
| 584 | QString dest; |
| 585 | QFile f; |
| 586 | |
| 587 | // Loop until we got 0 (end of data) |
| 588 | do { |
| 589 | QByteArray buffer; |
| 590 | dataReq(); // Request for data |
| 591 | result = readData(buffer); |
| 592 | |
| 593 | if (result >= 0) { |
| 594 | if (dest.isEmpty()) { |
| 595 | if (bMarkPartial) { |
| 596 | // qDebug() << "Appending .part extension to" << dest_orig; |
| 597 | dest = dest_part; |
| 598 | if (bPartExists && !(_flags & KIO::Resume)) { |
| 599 | // qDebug() << "Deleting partial file" << dest_part; |
| 600 | QFile::remove(fileName: dest_part); |
| 601 | // Catch errors when we try to open the file. |
| 602 | } |
| 603 | } else { |
| 604 | dest = dest_orig; |
| 605 | if (bOrigExists && !(_flags & KIO::Resume)) { |
| 606 | // qDebug() << "Deleting destination file" << dest_orig; |
| 607 | QFile::remove(fileName: dest_orig); |
| 608 | // Catch errors when we try to open the file. |
| 609 | } |
| 610 | } |
| 611 | |
| 612 | f.setFileName(dest); |
| 613 | |
| 614 | if ((_flags & KIO::Resume)) { |
| 615 | f.open(flags: QIODevice::ReadWrite | QIODevice::Append); |
| 616 | } else { |
| 617 | f.open(flags: QIODevice::Truncate | QIODevice::WriteOnly); |
| 618 | if (_mode != -1) { |
| 619 | // WABA: Make sure that we keep writing permissions ourselves, |
| 620 | // otherwise we can be in for a surprise on NFS. |
| 621 | mode_t initialMode = _mode | S_IWUSR | S_IRUSR; |
| 622 | f.setPermissions(modeToQFilePermissions(mode: initialMode)); |
| 623 | } |
| 624 | } |
| 625 | |
| 626 | if (!f.isOpen()) { |
| 627 | int oflags = 0; |
| 628 | int filemode = _mode; |
| 629 | |
| 630 | if ((_flags & KIO::Resume)) { |
| 631 | oflags = O_RDWR | O_APPEND; |
| 632 | } else { |
| 633 | oflags = O_WRONLY | O_TRUNC | O_CREAT; |
| 634 | if (_mode != -1) { |
| 635 | filemode = _mode | S_IWUSR | S_IRUSR; |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | auto result = tryOpen(f, path: QFile::encodeName(fileName: dest), flags: oflags, mode: filemode, errno); |
| 640 | if (!result.success()) { |
| 641 | if (!resultWasCancelled(result)) { |
| 642 | // qDebug() << "####################### COULD NOT WRITE" << dest << "_mode=" << _mode; |
| 643 | // qDebug() << "QFile error==" << f.error() << "(" << f.errorString() << ")"; |
| 644 | |
| 645 | if (f.error() == QFileDevice::PermissionsError) { |
| 646 | return WorkerResult::fail(error: KIO::ERR_WRITE_ACCESS_DENIED, errorString: dest); |
| 647 | } else { |
| 648 | return WorkerResult::fail(error: KIO::ERR_CANNOT_OPEN_FOR_WRITING, errorString: dest); |
| 649 | } |
| 650 | } |
| 651 | return WorkerResult::pass(); |
| 652 | } else { |
| 653 | #ifndef Q_OS_WIN |
| 654 | if ((_flags & KIO::Resume)) { |
| 655 | execWithElevatedPrivilege(action: CHOWN, args: {dest, getuid(), getgid()}, errno); |
| 656 | QFile::setPermissions(filename: dest, permissionSpec: modeToQFilePermissions(mode: filemode)); |
| 657 | } |
| 658 | #endif |
| 659 | } |
| 660 | } |
| 661 | } |
| 662 | |
| 663 | if (f.write(data: buffer) == -1) { |
| 664 | if (f.error() == QFile::ResourceError) { // disk full |
| 665 | error = KIO::ERR_DISK_FULL; |
| 666 | result = -2; // means: remove dest file |
| 667 | } else { |
| 668 | qCWarning(KIO_FILE) << "Couldn't write. Error:" << f.errorString(); |
| 669 | error = KIO::ERR_CANNOT_WRITE; |
| 670 | } |
| 671 | } |
| 672 | } else { |
| 673 | qCWarning(KIO_FILE) << "readData() returned" << result; |
| 674 | error = KIO::ERR_CANNOT_WRITE; |
| 675 | } |
| 676 | } while (result > 0); |
| 677 | |
| 678 | // An error occurred deal with it. |
| 679 | if (result < 0) { |
| 680 | // qDebug() << "Error during 'put'. Aborting."; |
| 681 | |
| 682 | if (f.isOpen()) { |
| 683 | f.close(); |
| 684 | |
| 685 | QT_STATBUF buff; |
| 686 | if (QT_STAT(file: QFile::encodeName(fileName: dest).constData(), buf: &buff) == 0) { |
| 687 | int size = configValue(QStringLiteral("MinimumKeepSize" ), defaultValue: DEFAULT_MINIMUM_KEEP_SIZE); |
| 688 | if (buff.st_size < size) { |
| 689 | QFile::remove(fileName: dest); |
| 690 | } |
| 691 | } |
| 692 | } |
| 693 | return WorkerResult::fail(error: error, errorString: dest_orig); |
| 694 | } |
| 695 | |
| 696 | if (!f.isOpen()) { // we got nothing to write out, so we never opened the file |
| 697 | return WorkerResult::pass(); |
| 698 | } |
| 699 | |
| 700 | f.close(); |
| 701 | |
| 702 | if (f.error() != QFile::NoError) { |
| 703 | qCWarning(KIO_FILE) << "Error when closing file descriptor:" << f.errorString(); |
| 704 | return WorkerResult::fail(error: KIO::ERR_CANNOT_WRITE, errorString: dest_orig); |
| 705 | } |
| 706 | |
| 707 | // after full download rename the file back to original name |
| 708 | if (bMarkPartial) { |
| 709 | // QFile::rename() never overwrites the destination file unlike ::remove, |
| 710 | // so we must remove it manually first |
| 711 | if (_flags & KIO::Overwrite) { |
| 712 | if (!QFile::remove(fileName: dest_orig)) { |
| 713 | execWithElevatedPrivilege(action: DEL, args: {dest_orig}, errno); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | if (!QFile::rename(oldName: dest, newName: dest_orig)) { |
| 718 | auto result = execWithElevatedPrivilege(action: RENAME, args: {dest, dest_orig}, errno); |
| 719 | if (!result.success()) { |
| 720 | if (!resultWasCancelled(result)) { |
| 721 | qCWarning(KIO_FILE) << " Couldn't rename " << dest << " to " << dest_orig; |
| 722 | return WorkerResult::fail(error: KIO::ERR_CANNOT_RENAME_PARTIAL, errorString: dest_orig); |
| 723 | } |
| 724 | return WorkerResult::pass(); |
| 725 | } |
| 726 | } |
| 727 | #ifdef WITH_QTDBUS |
| 728 | org::kde::KDirNotify::emitFileRenamed(src: QUrl::fromLocalFile(localfile: dest), dst: QUrl::fromLocalFile(localfile: dest_orig)); |
| 729 | #endif |
| 730 | } |
| 731 | |
| 732 | // set final permissions |
| 733 | if (_mode != -1 && !(_flags & KIO::Resume)) { |
| 734 | if (!QFile::setPermissions(filename: dest_orig, permissionSpec: modeToQFilePermissions(mode: _mode))) { |
| 735 | // couldn't chmod. Eat the error if the filesystem apparently doesn't support it. |
| 736 | KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(path: dest_orig); |
| 737 | if (mp && mp->testFileSystemFlag(flag: KMountPoint::SupportsChmod)) { |
| 738 | if (!tryChangeFileAttr(action: CHMOD, args: {dest_orig, _mode}, errno).success()) { |
| 739 | warning(i18n("Could not change permissions for\n%1" , dest_orig)); |
| 740 | } |
| 741 | } |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | // set original owner and group |
| 746 | #if !defined(Q_OS_WIN) |
| 747 | if (bOrigExists) { |
| 748 | if (::chown(qUtf8Printable(dest_orig), owner: owner, group: group) < 0) { |
| 749 | warning(i18nc("@info" , "Could not change owner and group for\n%1" , dest_orig)); |
| 750 | } |
| 751 | } |
| 752 | #endif |
| 753 | |
| 754 | // set modification time |
| 755 | const QString mtimeStr = metaData(QStringLiteral("modified" )); |
| 756 | if (!mtimeStr.isEmpty()) { |
| 757 | QDateTime dt = QDateTime::fromString(string: mtimeStr, format: Qt::ISODate); |
| 758 | if (dt.isValid()) { |
| 759 | QT_STATBUF dest_statbuf; |
| 760 | if (QT_STAT(file: QFile::encodeName(fileName: dest_orig).constData(), buf: &dest_statbuf) == 0) { |
| 761 | #ifndef Q_OS_WIN |
| 762 | struct timeval utbuf[2]; |
| 763 | // access time |
| 764 | utbuf[0].tv_sec = dest_statbuf.st_atime; // access time, unchanged ## TODO preserve msec |
| 765 | utbuf[0].tv_usec = 0; |
| 766 | // modification time |
| 767 | utbuf[1].tv_sec = dt.toSecsSinceEpoch(); |
| 768 | utbuf[1].tv_usec = dt.time().msec() * 1000; |
| 769 | utimes(file: QFile::encodeName(fileName: dest_orig).constData(), tvp: utbuf); |
| 770 | #else |
| 771 | struct utimbuf utbuf; |
| 772 | utbuf.actime = dest_statbuf.st_atime; |
| 773 | utbuf.modtime = dt.toSecsSinceEpoch(); |
| 774 | if (utime(QFile::encodeName(dest_orig).constData(), &utbuf) != 0) { |
| 775 | tryChangeFileAttr(UTIME, {dest_orig, qint64(utbuf.actime), qint64(utbuf.modtime)}, errno); |
| 776 | } |
| 777 | #endif |
| 778 | } |
| 779 | } |
| 780 | } |
| 781 | |
| 782 | // We have done our job => finish |
| 783 | return WorkerResult::pass(); |
| 784 | } |
| 785 | |
| 786 | WorkerResult FileProtocol::special(const QByteArray &data) |
| 787 | { |
| 788 | int tmp; |
| 789 | QDataStream stream(data); |
| 790 | |
| 791 | stream >> tmp; |
| 792 | switch (tmp) { |
| 793 | case 1: { |
| 794 | QString fstype; |
| 795 | QString dev; |
| 796 | QString point; |
| 797 | qint8 iRo; |
| 798 | |
| 799 | stream >> iRo >> fstype >> dev >> point; |
| 800 | |
| 801 | bool ro = (iRo != 0); |
| 802 | |
| 803 | // qDebug() << "MOUNTING fstype=" << fstype << " dev=" << dev << " point=" << point << " ro=" << ro; |
| 804 | return mount(ro: ro, fstype: fstype.toLatin1().constData(), dev, point); |
| 805 | } |
| 806 | case 2: { |
| 807 | QString point; |
| 808 | stream >> point; |
| 809 | return unmount(point); |
| 810 | } |
| 811 | default: |
| 812 | break; |
| 813 | } |
| 814 | return WorkerResult::pass(); |
| 815 | } |
| 816 | |
| 817 | static QStringList fallbackSystemPath() |
| 818 | { |
| 819 | return QStringList{ |
| 820 | QStringLiteral("/sbin" ), |
| 821 | QStringLiteral("/bin" ), |
| 822 | }; |
| 823 | } |
| 824 | |
| 825 | WorkerResult FileProtocol::mount(bool _ro, const char *_fstype, const QString &_dev, const QString &_point) |
| 826 | { |
| 827 | // qDebug() << "fstype=" << _fstype; |
| 828 | |
| 829 | const QLatin1String label("LABEL=" ); |
| 830 | const QLatin1String uuid("UUID=" ); |
| 831 | QTemporaryFile tmpFile; |
| 832 | tmpFile.setAutoRemove(false); |
| 833 | tmpFile.open(); |
| 834 | QByteArray tmpFileName = QFile::encodeName(fileName: tmpFile.fileName()); |
| 835 | QByteArray dev; |
| 836 | if (_dev.startsWith(s: label)) { // turn LABEL=foo into -L foo (#71430) |
| 837 | QString labelName = _dev.mid(position: label.size()); |
| 838 | dev = "-L " + QFile::encodeName(fileName: KShell::quoteArg(arg: labelName)); // is it correct to assume same encoding as filesystem? |
| 839 | } else if (_dev.startsWith(s: uuid)) { // and UUID=bar into -U bar |
| 840 | QString uuidName = _dev.mid(position: uuid.size()); |
| 841 | dev = "-U " + QFile::encodeName(fileName: KShell::quoteArg(arg: uuidName)); |
| 842 | } else { |
| 843 | dev = QFile::encodeName(fileName: KShell::quoteArg(arg: _dev)); // get those ready to be given to a shell |
| 844 | } |
| 845 | |
| 846 | QByteArray point = QFile::encodeName(fileName: KShell::quoteArg(arg: _point)); |
| 847 | bool fstype_empty = !_fstype || !*_fstype; |
| 848 | QByteArray fstype = KShell::quoteArg(arg: QString::fromLatin1(ba: _fstype)).toLatin1(); // good guess |
| 849 | QByteArray readonly = _ro ? "-r" : "" ; |
| 850 | QByteArray mountProg = QStandardPaths::findExecutable(QStringLiteral("mount" )).toLocal8Bit(); |
| 851 | if (mountProg.isEmpty()) { |
| 852 | mountProg = QStandardPaths::findExecutable(QStringLiteral("mount" ), paths: fallbackSystemPath()).toLocal8Bit(); |
| 853 | } |
| 854 | if (mountProg.isEmpty()) { |
| 855 | return WorkerResult::fail(error: KIO::ERR_CANNOT_MOUNT, i18n("Could not find program \"mount\"" )); |
| 856 | } |
| 857 | |
| 858 | // Two steps, in case mount doesn't like it when we pass all options |
| 859 | for (int step = 0; step <= 1; step++) { |
| 860 | QByteArray buffer = mountProg + ' '; |
| 861 | // Mount using device only if no fstype nor mountpoint (KDE-1.x like) |
| 862 | if (!dev.isEmpty() && _point.isEmpty() && fstype_empty) { |
| 863 | buffer += dev; |
| 864 | } else if (!_point.isEmpty() && dev.isEmpty() && fstype_empty) { |
| 865 | // Mount using the mountpoint, if no fstype nor device (impossible in first step) |
| 866 | buffer += point; |
| 867 | } else if (!_point.isEmpty() && !dev.isEmpty() && fstype_empty) { // mount giving device + mountpoint but no fstype |
| 868 | buffer += readonly + ' ' + dev + ' ' + point; |
| 869 | } else { // mount giving device + mountpoint + fstype |
| 870 | buffer += readonly + " -t " + fstype + ' ' + dev + ' ' + point; |
| 871 | } |
| 872 | if (fstype == "ext2" || fstype == "ext3" || fstype == "ext4" ) { |
| 873 | buffer += " -o errors=remount-ro" ; |
| 874 | } |
| 875 | |
| 876 | buffer += " 2>" + tmpFileName; |
| 877 | // qDebug() << buffer; |
| 878 | |
| 879 | int mount_ret = system(command: buffer.constData()); |
| 880 | |
| 881 | QString err = readLogFile(filename: tmpFileName); |
| 882 | if (err.isEmpty() && mount_ret == 0) { |
| 883 | return WorkerResult::pass(); |
| 884 | } else { |
| 885 | // Didn't work - or maybe we just got a warning |
| 886 | KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(device: _dev); |
| 887 | // Is the device mounted ? |
| 888 | if (mp && mount_ret == 0) { |
| 889 | // qDebug() << "mount got a warning:" << err; |
| 890 | warning(msg: err); |
| 891 | return WorkerResult::pass(); |
| 892 | } else { |
| 893 | if ((step == 0) && !_point.isEmpty()) { |
| 894 | // qDebug() << err; |
| 895 | // qDebug() << "Mounting with those options didn't work, trying with only mountpoint"; |
| 896 | fstype = "" ; |
| 897 | fstype_empty = true; |
| 898 | dev = "" ; |
| 899 | // The reason for trying with only mountpoint (instead of |
| 900 | // only device) is that some people (hi Malte!) have the |
| 901 | // same device associated with two mountpoints |
| 902 | // for different fstypes, like /dev/fd0 /mnt/e2floppy and |
| 903 | // /dev/fd0 /mnt/dosfloppy. |
| 904 | // If the user has the same mountpoint associated with two |
| 905 | // different devices, well they shouldn't specify the |
| 906 | // mountpoint but just the device. |
| 907 | } else { |
| 908 | return WorkerResult::fail(error: KIO::ERR_CANNOT_MOUNT, errorString: err); |
| 909 | } |
| 910 | } |
| 911 | } |
| 912 | } |
| 913 | return WorkerResult::pass(); |
| 914 | } |
| 915 | |
| 916 | WorkerResult FileProtocol::unmount(const QString &_point) |
| 917 | { |
| 918 | QByteArray buffer; |
| 919 | |
| 920 | QTemporaryFile tmpFile; |
| 921 | tmpFile.setAutoRemove(false); |
| 922 | tmpFile.open(); |
| 923 | |
| 924 | QByteArray umountProg = QStandardPaths::findExecutable(QStringLiteral("umount" )).toLocal8Bit(); |
| 925 | if (umountProg.isEmpty()) { |
| 926 | umountProg = QStandardPaths::findExecutable(QStringLiteral("umount" ), paths: fallbackSystemPath()).toLocal8Bit(); |
| 927 | } |
| 928 | if (umountProg.isEmpty()) { |
| 929 | return WorkerResult::fail(error: KIO::ERR_CANNOT_UNMOUNT, i18n("Could not find program \"umount\"" )); |
| 930 | } |
| 931 | |
| 932 | QByteArray tmpFileName = QFile::encodeName(fileName: tmpFile.fileName()); |
| 933 | |
| 934 | buffer = umountProg + ' ' + QFile::encodeName(fileName: KShell::quoteArg(arg: _point)) + " 2>" + tmpFileName; |
| 935 | system(command: buffer.constData()); |
| 936 | |
| 937 | QString err = readLogFile(filename: tmpFileName); |
| 938 | if (err.isEmpty()) { |
| 939 | return WorkerResult::pass(); |
| 940 | } else { |
| 941 | return WorkerResult::fail(error: KIO::ERR_CANNOT_UNMOUNT, errorString: err); |
| 942 | } |
| 943 | } |
| 944 | |
| 945 | /*!*********************************** |
| 946 | * |
| 947 | * Utilities |
| 948 | * |
| 949 | *************************************/ |
| 950 | |
| 951 | static QString readLogFile(const QByteArray &_filename) |
| 952 | { |
| 953 | QString result; |
| 954 | QFile file(QFile::decodeName(localFileName: _filename)); |
| 955 | if (file.open(flags: QIODevice::ReadOnly)) { |
| 956 | result = QString::fromLocal8Bit(ba: file.readAll()); |
| 957 | } |
| 958 | (void)file.remove(); |
| 959 | return result; |
| 960 | } |
| 961 | |
| 962 | // We could port this to KTempDir::removeDir but then we wouldn't be able to tell the user |
| 963 | // where exactly the deletion failed, in case of errors. |
| 964 | WorkerResult FileProtocol::deleteRecursive(const QString &path) |
| 965 | { |
| 966 | // qDebug() << path; |
| 967 | QDirIterator it(path, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System | QDir::Hidden, QDirIterator::Subdirectories); |
| 968 | QStringList dirsToDelete; |
| 969 | while (it.hasNext()) { |
| 970 | const QString itemPath = it.next(); |
| 971 | // qDebug() << "itemPath=" << itemPath; |
| 972 | const QFileInfo info = it.fileInfo(); |
| 973 | if (info.isDir() && !info.isSymLink()) { |
| 974 | dirsToDelete.prepend(t: itemPath); |
| 975 | } else { |
| 976 | // qDebug() << "QFile::remove" << itemPath; |
| 977 | if (!QFile::remove(fileName: itemPath)) { |
| 978 | auto result = execWithElevatedPrivilege(action: DEL, args: {itemPath}, errno); |
| 979 | if (!result.success()) { |
| 980 | if (!resultWasCancelled(result)) { |
| 981 | return WorkerResult::fail(error: KIO::ERR_CANNOT_DELETE, errorString: itemPath); |
| 982 | } |
| 983 | return result; |
| 984 | } |
| 985 | } |
| 986 | } |
| 987 | } |
| 988 | QDir dir; |
| 989 | for (const QString &itemPath : std::as_const(t&: dirsToDelete)) { |
| 990 | // qDebug() << "QDir::rmdir" << itemPath; |
| 991 | if (!dir.rmdir(dirName: itemPath)) { |
| 992 | auto result = execWithElevatedPrivilege(action: RMDIR, args: {itemPath}, errno); |
| 993 | if (!result.success()) { |
| 994 | if (!resultWasCancelled(result)) { |
| 995 | return WorkerResult::fail(error: KIO::ERR_CANNOT_DELETE, errorString: itemPath); |
| 996 | } |
| 997 | return result; |
| 998 | } |
| 999 | } |
| 1000 | } |
| 1001 | return WorkerResult::pass(); |
| 1002 | } |
| 1003 | |
| 1004 | WorkerResult FileProtocol::fileSystemFreeSpace(const QUrl &url) |
| 1005 | { |
| 1006 | if (url.isLocalFile()) { |
| 1007 | QStorageInfo storageInfo(url.toLocalFile()); |
| 1008 | if (storageInfo.isValid() && storageInfo.isReady()) { |
| 1009 | setMetaData(QStringLiteral("total" ), value: QString::number(storageInfo.bytesTotal())); |
| 1010 | setMetaData(QStringLiteral("available" ), value: QString::number(storageInfo.bytesAvailable())); |
| 1011 | |
| 1012 | return WorkerResult::pass(); |
| 1013 | } else { |
| 1014 | return WorkerResult::fail(error: KIO::ERR_CANNOT_STAT, errorString: url.url()); |
| 1015 | } |
| 1016 | } else { |
| 1017 | return WorkerResult::fail(error: KIO::ERR_UNSUPPORTED_PROTOCOL, errorString: url.url()); |
| 1018 | } |
| 1019 | } |
| 1020 | |
| 1021 | // needed for JSON file embedding |
| 1022 | #include "file.moc" |
| 1023 | |
| 1024 | #include "moc_file.cpp" |
| 1025 | |