File indexing completed on 2023-10-03 03:19:51
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "chmodjob.h" 0011 #include "../utils_p.h" 0012 0013 #include <KLocalizedString> 0014 #include <KUser> 0015 #include <QDebug> 0016 0017 #include "askuseractioninterface.h" 0018 #include "job_p.h" 0019 #include "jobuidelegatefactory.h" 0020 #include "kioglobal_p.h" 0021 #include "listjob.h" 0022 0023 #include <stack> 0024 0025 namespace KIO 0026 { 0027 struct ChmodInfo { 0028 QUrl url; 0029 int permissions; 0030 }; 0031 0032 enum ChmodJobState { 0033 CHMODJOB_STATE_LISTING, 0034 CHMODJOB_STATE_CHMODING, 0035 }; 0036 0037 class ChmodJobPrivate : public KIO::JobPrivate 0038 { 0039 public: 0040 ChmodJobPrivate(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive) 0041 : state(CHMODJOB_STATE_LISTING) 0042 , m_permissions(permissions) 0043 , m_mask(mask) 0044 , m_newOwner(newOwner) 0045 , m_newGroup(newGroup) 0046 , m_recursive(recursive) 0047 , m_bAutoSkipFiles(false) 0048 , m_lstItems(lstItems) 0049 { 0050 } 0051 0052 ChmodJobState state; 0053 int m_permissions; 0054 int m_mask; 0055 KUserId m_newOwner; 0056 KGroupId m_newGroup; 0057 bool m_recursive; 0058 bool m_bAutoSkipFiles; 0059 KFileItemList m_lstItems; 0060 std::stack<ChmodInfo> m_infos; 0061 0062 void chmodNextFile(); 0063 void slotEntries(KIO::Job *, const KIO::UDSEntryList &); 0064 void processList(); 0065 0066 Q_DECLARE_PUBLIC(ChmodJob) 0067 0068 static inline ChmodJob * 0069 newJob(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive, JobFlags flags) 0070 { 0071 ChmodJob *job = new ChmodJob(*new ChmodJobPrivate(lstItems, permissions, mask, newOwner, newGroup, recursive)); 0072 job->setUiDelegate(KIO::createDefaultJobUiDelegate()); 0073 if (!(flags & HideProgressInfo)) { 0074 KIO::getJobTracker()->registerJob(job); 0075 } 0076 if (!(flags & NoPrivilegeExecution)) { 0077 job->d_func()->m_privilegeExecutionEnabled = true; 0078 job->d_func()->m_operationType = ChangeAttr; 0079 } 0080 return job; 0081 } 0082 }; 0083 0084 } // namespace KIO 0085 0086 using namespace KIO; 0087 0088 ChmodJob::ChmodJob(ChmodJobPrivate &dd) 0089 : KIO::Job(dd) 0090 { 0091 Q_D(ChmodJob); 0092 auto processFunc = [d]() { 0093 d->processList(); 0094 }; 0095 QMetaObject::invokeMethod(this, processFunc, Qt::QueuedConnection); 0096 } 0097 0098 ChmodJob::~ChmodJob() 0099 { 0100 } 0101 0102 void ChmodJobPrivate::processList() 0103 { 0104 Q_Q(ChmodJob); 0105 while (!m_lstItems.isEmpty()) { 0106 const KFileItem item = m_lstItems.first(); 0107 if (!item.isLink()) { // don't do anything with symlinks 0108 // File or directory -> remember to chmod 0109 ChmodInfo info; 0110 info.url = item.url(); 0111 // This is a toplevel file, we apply changes directly (no +X emulation here) 0112 const mode_t permissions = item.permissions() & 0777; // get rid of "set gid" and other special flags 0113 info.permissions = (m_permissions & m_mask) | (permissions & ~m_mask); 0114 /*//qDebug() << "toplevel url:" << info.url << "\n current permissions=" << QString::number(permissions,8) 0115 << "\n wanted permission=" << QString::number(m_permissions,8) 0116 << "\n with mask=" << QString::number(m_mask,8) 0117 << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~m_mask,8) 0118 << "\n bits we keep =" << QString::number(permissions & ~m_mask,8) 0119 << "\n new permissions = " << QString::number(info.permissions,8);*/ 0120 m_infos.push(std::move(info)); 0121 // qDebug() << "processList : Adding info for " << info.url; 0122 // Directory and recursive -> list 0123 if (item.isDir() && m_recursive) { 0124 // qDebug() << "ChmodJob::processList dir -> listing"; 0125 KIO::ListJob *listJob = KIO::listRecursive(item.url(), KIO::HideProgressInfo); 0126 q->connect(listJob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &entries) { 0127 slotEntries(job, entries); 0128 }); 0129 q->addSubjob(listJob); 0130 return; // we'll come back later, when this one's finished 0131 } 0132 } 0133 m_lstItems.removeFirst(); 0134 } 0135 // qDebug() << "ChmodJob::processList -> going to STATE_CHMODING"; 0136 // We have finished, move on 0137 state = CHMODJOB_STATE_CHMODING; 0138 chmodNextFile(); 0139 } 0140 0141 void ChmodJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list) 0142 { 0143 KIO::UDSEntryList::ConstIterator it = list.begin(); 0144 KIO::UDSEntryList::ConstIterator end = list.end(); 0145 for (; it != end; ++it) { 0146 const KIO::UDSEntry &entry = *it; 0147 const bool isLink = !entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); 0148 const QString relativePath = entry.stringValue(KIO::UDSEntry::UDS_NAME); 0149 if (!isLink && relativePath != QLatin1String("..")) { 0150 const mode_t permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & 0777; // get rid of "set gid" and other special flags 0151 0152 ChmodInfo info; 0153 info.url = m_lstItems.first().url(); // base directory 0154 info.url.setPath(Utils::concatPaths(info.url.path(), relativePath)); 0155 int mask = m_mask; 0156 // Emulate -X: only give +x to files that had a +x bit already 0157 // So the check is the opposite : if the file had no x bit, don't touch x bits 0158 // For dirs this doesn't apply 0159 if (!entry.isDir()) { 0160 int newPerms = m_permissions & mask; 0161 if ((newPerms & 0111) && !(permissions & 0111)) { 0162 // don't interfere with mandatory file locking 0163 if (newPerms & 02000) { 0164 mask = mask & ~0101; 0165 } else { 0166 mask = mask & ~0111; 0167 } 0168 } 0169 } 0170 info.permissions = (m_permissions & mask) | (permissions & ~mask); 0171 /*//qDebug() << info.url << "\n current permissions=" << QString::number(permissions,8) 0172 << "\n wanted permission=" << QString::number(m_permissions,8) 0173 << "\n with mask=" << QString::number(mask,8) 0174 << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~mask,8) 0175 << "\n bits we keep =" << QString::number(permissions & ~mask,8) 0176 << "\n new permissions = " << QString::number(info.permissions,8);*/ 0177 // Push this info on top of the stack so it's handled first. 0178 // This way, the toplevel dirs are done last. 0179 m_infos.push(std::move(info)); 0180 } 0181 } 0182 } 0183 0184 void ChmodJobPrivate::chmodNextFile() 0185 { 0186 auto processNextFunc = [this]() { 0187 chmodNextFile(); 0188 }; 0189 0190 Q_Q(ChmodJob); 0191 if (!m_infos.empty()) { 0192 ChmodInfo info = m_infos.top(); 0193 m_infos.pop(); 0194 // First update group / owner (if local file) 0195 // (permissions have to be set after, in case of suid and sgid) 0196 if (info.url.isLocalFile() && (m_newOwner.isValid() || m_newGroup.isValid())) { 0197 QString path = info.url.toLocalFile(); 0198 if (!KIOPrivate::changeOwnership(path, m_newOwner, m_newGroup)) { 0199 auto *askUserActionInterface = KIO::delegateExtension<AskUserActionInterface *>(q); 0200 if (!askUserActionInterface) { 0201 Q_EMIT q->warning(q, i18n("Could not modify the ownership of file %1", path)); 0202 } else if (!m_bAutoSkipFiles) { 0203 SkipDialog_Options options; 0204 if (m_infos.size() > 1) { 0205 options |= SkipDialog_MultipleItems; 0206 } 0207 0208 auto skipSignal = &AskUserActionInterface::askUserSkipResult; 0209 q->connect(askUserActionInterface, skipSignal, q, [=](KIO::SkipDialog_Result result, KJob *parentJob) { 0210 Q_ASSERT(q == parentJob); 0211 q->disconnect(askUserActionInterface, skipSignal, q, nullptr); 0212 0213 switch (result) { 0214 case Result_AutoSkip: 0215 m_bAutoSkipFiles = true; 0216 // fall through 0217 Q_FALLTHROUGH(); 0218 case Result_Skip: 0219 QMetaObject::invokeMethod(q, processNextFunc, Qt::QueuedConnection); 0220 return; 0221 case Result_Retry: 0222 m_infos.push(std::move(info)); 0223 QMetaObject::invokeMethod(q, processNextFunc, Qt::QueuedConnection); 0224 return; 0225 case Result_Cancel: 0226 default: 0227 q->setError(ERR_USER_CANCELED); 0228 q->emitResult(); 0229 return; 0230 } 0231 }); 0232 0233 askUserActionInterface->askUserSkip(q, 0234 options, 0235 xi18n("Could not modify the ownership of file <filename>%1</filename>. You have " 0236 "insufficient access to the file to perform the change.", 0237 path)); 0238 return; 0239 } 0240 } 0241 } 0242 0243 /*qDebug() << "chmod'ing" << info.url << "to" << QString::number(info.permissions,8);*/ 0244 KIO::SimpleJob *job = KIO::chmod(info.url, info.permissions); 0245 job->setParentJob(q); 0246 // copy the metadata for acl and default acl 0247 const QString aclString = q->queryMetaData(QStringLiteral("ACL_STRING")); 0248 const QString defaultAclString = q->queryMetaData(QStringLiteral("DEFAULT_ACL_STRING")); 0249 if (!aclString.isEmpty()) { 0250 job->addMetaData(QStringLiteral("ACL_STRING"), aclString); 0251 } 0252 if (!defaultAclString.isEmpty()) { 0253 job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), defaultAclString); 0254 } 0255 q->addSubjob(job); 0256 } else { // We have finished 0257 q->emitResult(); 0258 } 0259 } 0260 0261 void ChmodJob::slotResult(KJob *job) 0262 { 0263 Q_D(ChmodJob); 0264 removeSubjob(job); 0265 if (job->error()) { 0266 setError(job->error()); 0267 setErrorText(job->errorText()); 0268 emitResult(); 0269 return; 0270 } 0271 // qDebug() << "d->m_lstItems:" << d->m_lstItems.count(); 0272 switch (d->state) { 0273 case CHMODJOB_STATE_LISTING: 0274 d->m_lstItems.removeFirst(); 0275 // qDebug() << "-> processList"; 0276 d->processList(); 0277 return; 0278 case CHMODJOB_STATE_CHMODING: 0279 // qDebug() << "-> chmodNextFile"; 0280 d->chmodNextFile(); 0281 return; 0282 default: 0283 Q_ASSERT(false); 0284 return; 0285 } 0286 } 0287 0288 ChmodJob *KIO::chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &owner, const QString &group, bool recursive, JobFlags flags) 0289 { 0290 KUserId uid = KUserId::fromName(owner); 0291 KGroupId gid = KGroupId::fromName(group); 0292 return ChmodJobPrivate::newJob(lstItems, permissions, mask, uid, gid, recursive, flags); 0293 } 0294 0295 #include "moc_chmodjob.cpp"