KIO

chmodjob.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "chmodjob.h"
11#include "../utils_p.h"
12
13#include <KLocalizedString>
14#include <KUser>
15#include <QDebug>
16
17#include "askuseractioninterface.h"
18#include "job_p.h"
19#include "jobuidelegatefactory.h"
20#include "kioglobal_p.h"
21#include "listjob.h"
22
23#include <stack>
24
25namespace KIO
26{
27struct ChmodInfo {
28 QUrl url;
29 int permissions;
30};
31
32enum ChmodJobState {
33 CHMODJOB_STATE_LISTING,
34 CHMODJOB_STATE_CHMODING,
35};
36
37class ChmodJobPrivate : public KIO::JobPrivate
38{
39public:
40 ChmodJobPrivate(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive)
41 : state(CHMODJOB_STATE_LISTING)
42 , m_permissions(permissions)
43 , m_mask(mask)
44 , m_newOwner(newOwner)
45 , m_newGroup(newGroup)
46 , m_recursive(recursive)
47 , m_bAutoSkipFiles(false)
48 , m_lstItems(lstItems)
49 {
50 }
51
52 ChmodJobState state;
53 int m_permissions;
54 int m_mask;
55 KUserId m_newOwner;
56 KGroupId m_newGroup;
57 bool m_recursive;
58 bool m_bAutoSkipFiles;
59 KFileItemList m_lstItems;
60 std::stack<ChmodInfo> m_infos;
61
62 void chmodNextFile();
63 void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
64 void processList();
65
66 Q_DECLARE_PUBLIC(ChmodJob)
67
68 static inline ChmodJob *
69 newJob(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive, JobFlags flags)
70 {
71 ChmodJob *job = new ChmodJob(*new ChmodJobPrivate(lstItems, permissions, mask, newOwner, newGroup, recursive));
73 if (!(flags & HideProgressInfo)) {
75 }
76 if (!(flags & NoPrivilegeExecution)) {
77 job->d_func()->m_privilegeExecutionEnabled = true;
78 job->d_func()->m_operationType = ChangeAttr;
79 }
80 return job;
81 }
82};
83
84} // namespace KIO
85
86using namespace KIO;
87
88ChmodJob::ChmodJob(ChmodJobPrivate &dd)
89 : KIO::Job(dd)
90{
92 auto processFunc = [d]() {
93 d->processList();
94 };
96}
97
98ChmodJob::~ChmodJob()
99{
100}
101
102void ChmodJobPrivate::processList()
103{
104 Q_Q(ChmodJob);
105 while (!m_lstItems.isEmpty()) {
106 const KFileItem item = m_lstItems.first();
107 if (!item.isLink()) { // don't do anything with symlinks
108 // File or directory -> remember to chmod
109 ChmodInfo info;
110 info.url = item.url();
111 // This is a toplevel file, we apply changes directly (no +X emulation here)
112 const mode_t permissions = item.permissions() & 0777; // get rid of "set gid" and other special flags
113 info.permissions = (m_permissions & m_mask) | (permissions & ~m_mask);
114 /*//qDebug() << "toplevel url:" << info.url << "\n current permissions=" << QString::number(permissions,8)
115 << "\n wanted permission=" << QString::number(m_permissions,8)
116 << "\n with mask=" << QString::number(m_mask,8)
117 << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~m_mask,8)
118 << "\n bits we keep =" << QString::number(permissions & ~m_mask,8)
119 << "\n new permissions = " << QString::number(info.permissions,8);*/
120 m_infos.push(std::move(info));
121 // qDebug() << "processList : Adding info for " << info.url;
122 // Directory and recursive -> list
123 if (item.isDir() && m_recursive) {
124 // qDebug() << "ChmodJob::processList dir -> listing";
126 q->connect(listJob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &entries) {
127 slotEntries(job, entries);
128 });
129 q->addSubjob(listJob);
130 return; // we'll come back later, when this one's finished
131 }
132 }
133 m_lstItems.removeFirst();
134 }
135 // qDebug() << "ChmodJob::processList -> going to STATE_CHMODING";
136 // We have finished, move on
137 state = CHMODJOB_STATE_CHMODING;
138 chmodNextFile();
139}
140
141void ChmodJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
142{
145 for (; it != end; ++it) {
146 const KIO::UDSEntry &entry = *it;
147 const bool isLink = !entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty();
148 const QString relativePath = entry.stringValue(KIO::UDSEntry::UDS_NAME);
149 if (!isLink && relativePath != QLatin1String("..")) {
150 const mode_t permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & 0777; // get rid of "set gid" and other special flags
151
152 ChmodInfo info;
153 info.url = m_lstItems.first().url(); // base directory
154 info.url.setPath(Utils::concatPaths(info.url.path(), relativePath));
155 int mask = m_mask;
156 // Emulate -X: only give +x to files that had a +x bit already
157 // So the check is the opposite : if the file had no x bit, don't touch x bits
158 // For dirs this doesn't apply
159 if (!entry.isDir()) {
160 int newPerms = m_permissions & mask;
161 if ((newPerms & 0111) && !(permissions & 0111)) {
162 // don't interfere with mandatory file locking
163 if (newPerms & 02000) {
164 mask = mask & ~0101;
165 } else {
166 mask = mask & ~0111;
167 }
168 }
169 }
170 info.permissions = (m_permissions & mask) | (permissions & ~mask);
171 /*//qDebug() << info.url << "\n current permissions=" << QString::number(permissions,8)
172 << "\n wanted permission=" << QString::number(m_permissions,8)
173 << "\n with mask=" << QString::number(mask,8)
174 << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~mask,8)
175 << "\n bits we keep =" << QString::number(permissions & ~mask,8)
176 << "\n new permissions = " << QString::number(info.permissions,8);*/
177 // Push this info on top of the stack so it's handled first.
178 // This way, the toplevel dirs are done last.
179 m_infos.push(std::move(info));
180 }
181 }
182}
183
184void ChmodJobPrivate::chmodNextFile()
185{
186 auto processNextFunc = [this]() {
187 chmodNextFile();
188 };
189
190 Q_Q(ChmodJob);
191 if (!m_infos.empty()) {
192 ChmodInfo info = m_infos.top();
193 m_infos.pop();
194 // First update group / owner (if local file)
195 // (permissions have to be set after, in case of suid and sgid)
196 if (info.url.isLocalFile() && (m_newOwner.isValid() || m_newGroup.isValid())) {
197 QString path = info.url.toLocalFile();
198 if (!KIOPrivate::changeOwnership(path, m_newOwner, m_newGroup)) {
199 auto *askUserActionInterface = KIO::delegateExtension<AskUserActionInterface *>(q);
200 if (!askUserActionInterface) {
201 Q_EMIT q->warning(q, i18n("Could not modify the ownership of file %1", path));
202 } else if (!m_bAutoSkipFiles) {
203 SkipDialog_Options options;
204 if (m_infos.size() > 1) {
205 options |= SkipDialog_MultipleItems;
206 }
207
209 q->connect(askUserActionInterface, skipSignal, q, [=, this](KIO::SkipDialog_Result result, KJob *parentJob) {
210 Q_ASSERT(q == parentJob);
211 q->disconnect(askUserActionInterface, skipSignal, q, nullptr);
212
213 switch (result) {
214 case Result_AutoSkip:
215 m_bAutoSkipFiles = true;
216 // fall through
217 Q_FALLTHROUGH();
218 case Result_Skip:
220 return;
221 case Result_Retry:
222 m_infos.push(std::move(info));
224 return;
225 case Result_Cancel:
226 default:
227 q->setError(ERR_USER_CANCELED);
228 q->emitResult();
229 return;
230 }
231 });
232
233 askUserActionInterface->askUserSkip(q,
234 options,
235 xi18n("Could not modify the ownership of file <filename>%1</filename>. You have "
236 "insufficient access to the file to perform the change.",
237 path));
238 return;
239 }
240 }
241 }
242
243 /*qDebug() << "chmod'ing" << info.url << "to" << QString::number(info.permissions,8);*/
244 KIO::SimpleJob *job = KIO::chmod(info.url, info.permissions);
245 job->setParentJob(q);
246 // copy the metadata for acl and default acl
247 const QString aclString = q->queryMetaData(QStringLiteral("ACL_STRING"));
248 const QString defaultAclString = q->queryMetaData(QStringLiteral("DEFAULT_ACL_STRING"));
249 if (!aclString.isEmpty()) {
250 job->addMetaData(QStringLiteral("ACL_STRING"), aclString);
251 }
252 if (!defaultAclString.isEmpty()) {
253 job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), defaultAclString);
254 }
255 q->addSubjob(job);
256 } else { // We have finished
257 q->emitResult();
258 }
259}
260
261void ChmodJob::slotResult(KJob *job)
262{
263 Q_D(ChmodJob);
264 removeSubjob(job);
265 if (job->error()) {
266 setError(job->error());
267 setErrorText(job->errorText());
268 emitResult();
269 return;
270 }
271 // qDebug() << "d->m_lstItems:" << d->m_lstItems.count();
272 switch (d->state) {
273 case CHMODJOB_STATE_LISTING:
274 d->m_lstItems.removeFirst();
275 // qDebug() << "-> processList";
276 d->processList();
277 return;
278 case CHMODJOB_STATE_CHMODING:
279 // qDebug() << "-> chmodNextFile";
280 d->chmodNextFile();
281 return;
282 default:
283 Q_ASSERT(false);
284 return;
285 }
286}
287
288ChmodJob *KIO::chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &owner, const QString &group, bool recursive, JobFlags flags)
289{
290 KUserId uid = KUserId::fromName(owner);
291 KGroupId gid = KGroupId::fromName(group);
292 return ChmodJobPrivate::newJob(lstItems, permissions, mask, uid, gid, recursive, flags);
293}
294
295#include "moc_chmodjob.cpp"
List of KFileItems, which adds a few helper methods to QList<KFileItem>.
Definition kfileitem.h:630
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
mode_t permissions() const
Returns the permissions of the file (stat.st_mode containing only permissions).
void askUserSkipResult(KIO::SkipDialog_Result result, KJob *parentJob)
Implementations of this interface must emit this signal when the skip dialog finishes,...
This job changes permissions on a list of files or directories, optionally in a recursive manner.
Definition chmodjob.h:28
The base class for all jobs.
Definition job_base.h:45
void setParentJob(Job *parentJob)
Set the parent Job.
Definition job.cpp:192
bool removeSubjob(KJob *job) override
Mark a sub job as being done.
Definition job.cpp:80
void addMetaData(const QString &key, const QString &value)
Add key/value pair to the meta data that is sent to the worker.
Definition job.cpp:221
A ListJob is allows you to get the get the content of a directory.
Definition listjob.h:28
void entries(KIO::Job *job, const KIO::UDSEntryList &list)
This signal emits the entry found by the job while listing.
A simple job (one url and one command).
Definition simplejob.h:27
Universal Directory Service.
Definition udsentry.h:78
QString stringValue(uint field) const
Definition udsentry.cpp:365
long long numberValue(uint field, long long defaultValue=0) const
Definition udsentry.cpp:370
@ UDS_LINK_DEST
Name of the file where the link points to Allows to check for a symlink (don't use S_ISLNK !...
Definition udsentry.h:245
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition udsentry.h:224
@ UDS_ACCESS
Access permissions (part of the mode returned by stat)
Definition udsentry.h:232
bool isDir() const
Definition udsentry.cpp:375
virtual void registerJob(KJob *job)
void setErrorText(const QString &errorText)
void emitResult()
int error() const
void setError(int errorCode)
QString errorText() const
void setUiDelegate(KJobUiDelegate *delegate)
QString xi18n(const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
A namespace for KIO globals.
KIOCORE_EXPORT ChmodJob * chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &newOwner, const QString &newGroup, bool recursive, JobFlags flags=DefaultFlags)
Creates a job that changes permissions/ownership on several files or directories, optionally recursiv...
Definition chmodjob.cpp:288
@ SkipDialog_MultipleItems
Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply t...
RenameDialog_Result
The result of a rename or skip dialog.
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
KIOCORE_EXPORT ListJob * listRecursive(const QUrl &url, JobFlags flags=DefaultFlags, ListJob::ListFlags listFlags=ListJob::ListFlag::IncludeHidden)
The same as the previous method, but recurses subdirectories.
Definition listjob.cpp:244
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
@ NoPrivilegeExecution
When set, notifies the worker that application/job does not want privilege execution.
Definition job_base.h:276
KIOCORE_EXPORT KJobTrackerInterface * getJobTracker()
Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
QString path(const QString &relativePath)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
const QList< QKeySequence > & end()
iterator begin()
iterator end()
T & first()
bool isEmpty() const const
void removeFirst()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
bool isEmpty() const const
QueuedConnection
bool isLocalFile() const const
QString path(ComponentFormattingOptions options) const const
void setPath(const QString &path, ParsingMode mode)
QString toLocalFile() const const
static KGroupId fromName(const QString &name)
static KUserId fromName(const QString &name)
bool isValid() const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 21 2024 11:57:29 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.