KIO

chmodjob.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2000 Stephan Kulow <[email protected]>
4  SPDX-FileCopyrightText: 2000 David Faure <[email protected]>
5  SPDX-FileCopyrightText: 2000 Waldo Bastian <[email protected]>
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 
25 namespace KIO
26 {
27 struct ChmodInfo {
28  QUrl url;
29  int permissions;
30 };
31 
32 enum ChmodJobState {
33  CHMODJOB_STATE_LISTING,
34  CHMODJOB_STATE_CHMODING,
35 };
36 
37 class ChmodJobPrivate : public KIO::JobPrivate
38 {
39 public:
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 
86 using namespace KIO;
87 
88 ChmodJob::ChmodJob(ChmodJobPrivate &dd)
89  : KIO::Job(dd)
90 {
91  Q_D(ChmodJob);
92  auto processFunc = [d]() {
93  d->processList();
94  };
96 }
97 
98 ChmodJob::~ChmodJob()
99 {
100 }
101 
102 void 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";
125  KIO::ListJob *listJob = KIO::listRecursive(item.url(), KIO::HideProgressInfo);
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 
141 void 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 
184 void 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, [=](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:
219  QMetaObject::invokeMethod(q, processNextFunc, Qt::QueuedConnection);
220  return;
221  case Result_Retry:
222  m_infos.push(std::move(info));
223  QMetaObject::invokeMethod(q, processNextFunc, Qt::QueuedConnection);
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 
261 void 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 
288 ChmodJob *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"
T & first()
virtual void registerJob(KJob *job)
static KUserId fromName(const QString &name)
void askUserSkipResult(KIO::SkipDialog_Result result, KJob *parentJob)
Implementations of this interface must emit this signal when the skip dialog finishes,...
void setErrorText(const QString &errorText)
@ NoPrivilegeExecution
When set, notifies the worker that application/job does not want privilege execution.
Definition: job_base.h:300
bool isDir() const
Definition: udsentry.cpp:386
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:228
void entries(KIO::Job *job, const KIO::UDSEntryList &list)
This signal emits the entry found by the job while listing.
@ 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:270
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
Definition: krecentdirs.cpp:39
QString xi18n(const char *text, const TYPE &arg...)
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition: udsentry.h:249
void setParentJob(Job *parentJob)
Set the parent Job.
Definition: job.cpp:199
KIOCORE_EXPORT KJobTrackerInterface * getJobTracker()
Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
Definition: jobtracker.cpp:14
QString stringValue(uint field) const
Definition: udsentry.cpp:376
KIOCORE_EXPORT ListJob * listRecursive(const QUrl &url, JobFlags flags=DefaultFlags, bool includeHidden=true)
The same as the previous method, but recurses subdirectories.
Definition: listjob.cpp:248
QString i18n(const char *text, const TYPE &arg...)
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
bool isEmpty() const const
void removeFirst()
QString errorText() const
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
QueuedConnection
bool isEmpty() const const
static KGroupId fromName(const QString &name)
long long numberValue(uint field, long long defaultValue=0) const
Definition: udsentry.cpp:381
@ SkipDialog_MultipleItems
Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply t...
QString path(const QString &relativePath)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void setUiDelegate(KJobUiDelegate *delegate)
A namespace for KIO globals.
QList::iterator begin()
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition: job_base.h:275
@ UDS_ACCESS
Access permissions (part of the mode returned by stat)
Definition: udsentry.h:257
void emitResult()
RenameDialog_Result
The result of a rename or skip dialog.
int error() const
mode_t permissions() const
Returns the permissions of the file (stat.st_mode containing only permissions).
Definition: kfileitem.cpp:1565
QList::iterator end()
bool removeSubjob(KJob *job) override
Mark a sub job as being done.
Definition: job.cpp:87
void setError(int errorCode)
const QList< QKeySequence > & end()
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Thu Nov 30 2023 03:52:29 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.