File indexing completed on 2024-03-24 15:27:26

0001 /*
0002   This file is part of the KDE libraries
0003   Copyright 1999 Waldo Bastian <bastian@kde.org>
0004   Copyright 2006 Allen Winter <winter@kde.org>
0005   Copyright 2006 Gregory S. Hayes <syncomm@kde.org>
0006   Copyright 2006 Jaison Lee <lee.jaison@gmail.com>
0007 
0008   This library is free software; you can redistribute it and/or
0009   modify it under the terms of the GNU Library General Public
0010   License version 2 as published by the Free Software Foundation.
0011 
0012   This library is distributed in the hope that it will be useful,
0013   but WITHOUT ANY WARRANTY; without even the implied warranty of
0014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015   Library General Public License for more details.
0016 
0017   You should have received a copy of the GNU Library General Public License
0018   along with this library; see the file COPYING.LIB.  If not, write to
0019   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020   Boston, MA 02110-1301, USA.
0021 */
0022 
0023 #include "ksavefile.h"
0024 #include <KLocalizedString>
0025 #include <QDir>
0026 #include <QTemporaryFile>
0027 
0028 #include <sys/types.h>
0029 #include <sys/stat.h> // umask, fchmod
0030 #include <unistd.h> // fchown, fdatasync
0031 
0032 #include <stdlib.h>
0033 #include <errno.h>
0034 
0035 #include <config-kdelibs4support.h>
0036 
0037 #ifdef Q_OS_WIN
0038 #include <kde_file_win.h>
0039 #endif
0040 
0041 static int s_umask;
0042 
0043 // Read umask before any threads are created to avoid race conditions
0044 static int kStoreUmask()
0045 {
0046     mode_t tmp = 0;
0047     s_umask = umask(tmp);
0048     return umask(s_umask);
0049 }
0050 
0051 Q_CONSTRUCTOR_FUNCTION(kStoreUmask)
0052 
0053 class Q_DECL_HIDDEN KSaveFile::Private
0054 {
0055 public:
0056     QString realFileName; //The name of the end-result file
0057     QString tempFileName; //The name of the temp file we are using
0058 
0059     QFile::FileError error;
0060     QString errorString;
0061     bool needFinalize;
0062     bool directWriteFallback;
0063 
0064     Private()
0065         : error(QFile::NoError),
0066           needFinalize(false),
0067           directWriteFallback(false)
0068     {
0069     }
0070 };
0071 
0072 KSaveFile::KSaveFile()
0073     : d(new Private())
0074 {
0075 }
0076 
0077 KSaveFile::KSaveFile(const QString &filename)
0078     : d(new Private())
0079 {
0080     KSaveFile::setFileName(filename);
0081 }
0082 
0083 KSaveFile::~KSaveFile()
0084 {
0085     finalize();
0086 
0087     delete d;
0088 }
0089 
0090 bool KSaveFile::open(OpenMode flags)
0091 {
0092     if (isOpen()) {
0093         return false;
0094     }
0095     d->needFinalize = false;
0096 
0097     if (d->realFileName.isEmpty()) {
0098         d->error = QFile::OpenError;
0099         d->errorString = i18n("No target filename has been given.");
0100         return false;
0101     }
0102 
0103     if (!d->tempFileName.isNull()) {
0104 #if 0 // do not set an error here, this open() fails, but the file itself is without errors
0105         d->error = QFile::OpenError;
0106         d->errorString = i18n("Already opened.");
0107 #endif
0108         return false;
0109     }
0110 
0111     //Create our temporary file
0112     QTemporaryFile tempFile;
0113     tempFile.setAutoRemove(false);
0114     tempFile.setFileTemplate(d->realFileName + QLatin1String("XXXXXX.new"));
0115     if (!tempFile.open()) {
0116 #ifdef Q_OS_UNIX
0117         if (d->directWriteFallback && errno == EACCES) {
0118             QFile::setFileName(d->realFileName);
0119             if (QFile::open(flags)) {
0120                 d->tempFileName.clear();
0121                 d->error = QFile::NoError;
0122                 d->needFinalize = true;
0123                 return true;
0124             }
0125         }
0126 #endif
0127 
0128         // we only check here if the directory can be written to
0129         // the actual filename isn't written to, but replaced later
0130         // with the contents of our tempfile
0131         const QFileInfo fileInfo(d->realFileName);
0132         QDir parentDir = fileInfo.dir();
0133         if (!QFileInfo(parentDir.absolutePath()).isWritable()) {
0134             d->error = QFile::PermissionsError;
0135             d->errorString = i18n("Insufficient permissions in target directory.");
0136             return false;
0137         }
0138         d->error = QFile::OpenError;
0139         d->errorString = i18n("Unable to open temporary file.");
0140         return false;
0141     }
0142 
0143     // if we're overwriting an existing file, ensure temp file's
0144     // permissions are the same as existing file so the existing
0145     // file's permissions are preserved. this will succeed completely
0146     // only if we are the same owner and group - or allmighty root.
0147     QFileInfo fi(d->realFileName);
0148     if (fi.exists()) {
0149         //Qt apparently has no way to change owner/group of file :(
0150         if (fchown(tempFile.handle(), fi.ownerId(), fi.groupId())) {
0151             // failed to set user and group => try to restore group only.
0152             fchown(tempFile.handle(), -1, fi.groupId());
0153         }
0154 
0155         tempFile.setPermissions(fi.permissions());
0156     } else {
0157         fchmod(tempFile.handle(), 0666 & (~s_umask));
0158     }
0159 
0160     //Open oursleves with the temporary file
0161     QFile::setFileName(tempFile.fileName());
0162     if (!QFile::open(flags)) {
0163         tempFile.setAutoRemove(true);
0164         return false;
0165     }
0166 
0167     d->tempFileName = tempFile.fileName();
0168     d->error = QFile::NoError;
0169     d->errorString.clear();
0170     d->needFinalize = true;
0171     return true;
0172 }
0173 
0174 void KSaveFile::setFileName(const QString &filename)
0175 {
0176     d->realFileName = filename;
0177 
0178     // make absolute if needed
0179     if (QDir::isRelativePath(filename)) {
0180         d->realFileName = QDir::current().absoluteFilePath(filename);
0181     }
0182 
0183     const QFileInfo fileInfo(d->realFileName);
0184     QDir parentDir = fileInfo.dir();
0185 
0186     // follow symbolic link, if any
0187     d->realFileName = parentDir.canonicalPath() + QLatin1Char('/') + fileInfo.fileName();
0188 }
0189 
0190 QFile::FileError KSaveFile::error() const
0191 {
0192     if (d->error != QFile::NoError) {
0193         return d->error;
0194     } else {
0195         return QFile::error();
0196     }
0197 }
0198 
0199 QString KSaveFile::errorString() const
0200 {
0201     if (!d->errorString.isEmpty()) {
0202         return d->errorString;
0203     } else {
0204         return QFile::errorString();
0205     }
0206 }
0207 
0208 QString KSaveFile::fileName() const
0209 {
0210     return d->realFileName;
0211 }
0212 
0213 void KSaveFile::abort()
0214 {
0215     close();
0216     if (!d->tempFileName.isEmpty()) {
0217         QFile::remove(d->tempFileName); //non-static QFile::remove() does not work.
0218         d->needFinalize = false;
0219     }
0220 }
0221 
0222 #if HAVE_FDATASYNC
0223 #  define FDATASYNC fdatasync
0224 #else
0225 #  define FDATASYNC fsync
0226 #endif
0227 
0228 bool KSaveFile::finalize()
0229 {
0230     if (!d->needFinalize) {
0231         return false;
0232     }
0233     bool success = false;
0234 #ifdef Q_OS_UNIX
0235     static int extraSync = -1;
0236     if (extraSync < 0) {
0237         extraSync = getenv("KDE_EXTRA_FSYNC") != nullptr ? 1 : 0;
0238     }
0239     if (extraSync) {
0240         if (flush()) {
0241             Q_FOREVER {
0242             if (!FDATASYNC(handle()))
0243                 {
0244                     break;
0245                 }
0246                 if (errno != EINTR)
0247                 {
0248                     d->error = QFile::WriteError;
0249                     d->errorString = i18n("Synchronization to disk failed");
0250                     break;
0251                 }
0252             }
0253         }
0254     }
0255 #endif
0256 
0257     close();
0258 
0259     if (!d->tempFileName.isEmpty()) {
0260         if (error() != NoError) {
0261             QFile::remove(d->tempFileName);
0262         }
0263         //Qt does not allow us to atomically overwrite an existing file,
0264         //so if the target file already exists, there is no way to change it
0265         //to the temp file without creating a small race condition. So we use
0266         //the standard rename call instead, which will do the copy without the
0267         //race condition.
0268 #ifdef Q_OS_WIN
0269         else if (0 == kdewin32_rename(QFile::encodeName(d->tempFileName).constData(),
0270                                       QFile::encodeName(d->realFileName).constData())) {
0271 #else
0272         else if (0 == ::rename(QFile::encodeName(d->tempFileName).constData(),
0273                                QFile::encodeName(d->realFileName).constData())) {
0274 #endif
0275             d->error = QFile::NoError;
0276             d->errorString.clear();
0277             success = true;
0278         } else {
0279             d->error = QFile::OpenError;
0280             d->errorString = i18n("Error during rename.");
0281             QFile::remove(d->tempFileName);
0282         }
0283     } else { // direct overwrite
0284         success = true;
0285     }
0286     d->needFinalize = false;
0287 
0288     return success;
0289 }
0290 
0291 #undef FDATASYNC
0292 
0293 void KSaveFile::setDirectWriteFallback(bool enabled)
0294 {
0295     d->directWriteFallback = enabled;
0296 }
0297 
0298 bool KSaveFile::directWriteFallback() const
0299 {
0300     return d->directWriteFallback;
0301 }
0302