File indexing completed on 2024-09-29 12:04:15
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