1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | // $Id: kacl.cpp 424977 2005-06-13 15:13:22Z tilladam $ |
8 | |
9 | #include <config-kiocore.h> |
10 | |
11 | #include "kacl.h" |
12 | |
13 | #if HAVE_POSIX_ACL |
14 | #include "../aclhelpers_p.h" |
15 | #endif |
16 | |
17 | #include <QDataStream> |
18 | #include <QHash> |
19 | #include <QString> |
20 | |
21 | #if HAVE_POSIX_ACL |
22 | using namespace KIO; |
23 | #endif |
24 | |
25 | class Q_DECL_HIDDEN KACL::KACLPrivate |
26 | { |
27 | public: |
28 | KACLPrivate() |
29 | #if HAVE_POSIX_ACL |
30 | : m_acl(nullptr) |
31 | #endif |
32 | { |
33 | } |
34 | #if HAVE_POSIX_ACL |
35 | explicit KACLPrivate(acl_t acl) |
36 | : m_acl(acl) |
37 | { |
38 | } |
39 | #endif |
40 | #if HAVE_POSIX_ACL |
41 | ~KACLPrivate() |
42 | { |
43 | if (m_acl) { |
44 | acl_free(m_acl); |
45 | } |
46 | } |
47 | #endif |
48 | // helpers |
49 | #if HAVE_POSIX_ACL |
50 | bool setMaskPermissions(unsigned short v); |
51 | QString getUserName(uid_t uid) const; |
52 | QString getGroupName(gid_t gid) const; |
53 | bool setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type); |
54 | bool setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type); |
55 | |
56 | acl_t m_acl; |
57 | mutable QHash<uid_t, QString> m_usercache; |
58 | mutable QHash<gid_t, QString> m_groupcache; |
59 | #endif |
60 | }; |
61 | |
62 | KACL::KACL(const QString &aclString) |
63 | : d(new KACLPrivate) |
64 | { |
65 | setACL(aclString); |
66 | } |
67 | |
68 | KACL::KACL(mode_t basePermissions) |
69 | #if HAVE_POSIX_ACL |
70 | : d(new KACLPrivate(ACLPortability::acl_from_mode(basePermissions))) |
71 | #else |
72 | : d(new KACLPrivate) |
73 | #endif |
74 | { |
75 | #if !HAVE_POSIX_ACL |
76 | Q_UNUSED(basePermissions); |
77 | #endif |
78 | } |
79 | |
80 | KACL::KACL() |
81 | : d(new KACLPrivate) |
82 | { |
83 | } |
84 | |
85 | KACL::KACL(const KACL &rhs) |
86 | : d(new KACLPrivate) |
87 | { |
88 | setACL(rhs.asString()); |
89 | } |
90 | |
91 | KACL::~KACL() = default; |
92 | |
93 | KACL &KACL::operator=(const KACL &rhs) |
94 | { |
95 | if (this != &rhs) { |
96 | setACL(rhs.asString()); |
97 | } |
98 | return *this; |
99 | } |
100 | |
101 | bool KACL::operator==(const KACL &rhs) const |
102 | { |
103 | #if HAVE_POSIX_ACL |
104 | return (ACLPortability::acl_cmp(d->m_acl, rhs.d->m_acl) == 0); |
105 | #else |
106 | Q_UNUSED(rhs); |
107 | return true; |
108 | #endif |
109 | } |
110 | |
111 | bool KACL::operator!=(const KACL &rhs) const |
112 | { |
113 | return !operator==(rhs); |
114 | } |
115 | |
116 | bool KACL::isValid() const |
117 | { |
118 | bool valid = false; |
119 | #if HAVE_POSIX_ACL |
120 | if (d->m_acl) { |
121 | valid = (acl_valid(d->m_acl) == 0); |
122 | } |
123 | #endif |
124 | return valid; |
125 | } |
126 | |
127 | bool KACL::isExtended() const |
128 | { |
129 | #if HAVE_POSIX_ACL |
130 | return (ACLPortability::acl_equiv_mode(d->m_acl, nullptr) != 0); |
131 | #else |
132 | return false; |
133 | #endif |
134 | } |
135 | |
136 | #if HAVE_POSIX_ACL |
137 | static acl_entry_t entryForTag(acl_t acl, acl_tag_t tag) |
138 | { |
139 | acl_entry_t entry; |
140 | int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); |
141 | while (ret == 1) { |
142 | acl_tag_t currentTag; |
143 | acl_get_tag_type(entry, ¤tTag); |
144 | if (currentTag == tag) { |
145 | return entry; |
146 | } |
147 | ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); |
148 | } |
149 | return nullptr; |
150 | } |
151 | |
152 | static unsigned short entryToPermissions(acl_entry_t entry) |
153 | { |
154 | if (entry == nullptr) { |
155 | return 0; |
156 | } |
157 | acl_permset_t permset; |
158 | if (acl_get_permset(entry, &permset) != 0) { |
159 | return 0; |
160 | } |
161 | return (ACLPortability::acl_get_perm(permset, ACL_READ) << 2 | ACLPortability::acl_get_perm(permset, ACL_WRITE) << 1 |
162 | | ACLPortability::acl_get_perm(permset, ACL_EXECUTE)); |
163 | } |
164 | |
165 | static void permissionsToEntry(acl_entry_t entry, unsigned short v) |
166 | { |
167 | if (entry == nullptr) { |
168 | return; |
169 | } |
170 | acl_permset_t permset; |
171 | if (acl_get_permset(entry, &permset) != 0) { |
172 | return; |
173 | } |
174 | acl_clear_perms(permset); |
175 | if (v & 4) { |
176 | acl_add_perm(permset, ACL_READ); |
177 | } |
178 | if (v & 2) { |
179 | acl_add_perm(permset, ACL_WRITE); |
180 | } |
181 | if (v & 1) { |
182 | acl_add_perm(permset, ACL_EXECUTE); |
183 | } |
184 | } |
185 | |
186 | static int getUidForName(const QString &name) |
187 | { |
188 | struct passwd *user = getpwnam(name.toLocal8Bit().constData()); |
189 | if (user) { |
190 | return user->pw_uid; |
191 | } else { |
192 | return -1; |
193 | } |
194 | } |
195 | |
196 | static int getGidForName(const QString &name) |
197 | { |
198 | struct group *group = getgrnam(name.toLocal8Bit().constData()); |
199 | if (group) { |
200 | return group->gr_gid; |
201 | } else { |
202 | return -1; |
203 | } |
204 | } |
205 | #endif |
206 | // ------------------ begin API implementation ------------ |
207 | |
208 | unsigned short KACL::ownerPermissions() const |
209 | { |
210 | #if HAVE_POSIX_ACL |
211 | return entryToPermissions(entryForTag(d->m_acl, ACL_USER_OBJ)); |
212 | #else |
213 | return 0; |
214 | #endif |
215 | } |
216 | |
217 | bool KACL::setOwnerPermissions(unsigned short v) |
218 | { |
219 | #if HAVE_POSIX_ACL |
220 | permissionsToEntry(entryForTag(d->m_acl, ACL_USER_OBJ), v); |
221 | #else |
222 | Q_UNUSED(v); |
223 | #endif |
224 | return true; |
225 | } |
226 | |
227 | unsigned short KACL::owningGroupPermissions() const |
228 | { |
229 | #if HAVE_POSIX_ACL |
230 | return entryToPermissions(entryForTag(d->m_acl, ACL_GROUP_OBJ)); |
231 | #else |
232 | return 0; |
233 | #endif |
234 | } |
235 | |
236 | bool KACL::setOwningGroupPermissions(unsigned short v) |
237 | { |
238 | #if HAVE_POSIX_ACL |
239 | permissionsToEntry(entryForTag(d->m_acl, ACL_GROUP_OBJ), v); |
240 | #else |
241 | Q_UNUSED(v); |
242 | #endif |
243 | return true; |
244 | } |
245 | |
246 | unsigned short KACL::othersPermissions() const |
247 | { |
248 | #if HAVE_POSIX_ACL |
249 | return entryToPermissions(entryForTag(d->m_acl, ACL_OTHER)); |
250 | #else |
251 | return 0; |
252 | #endif |
253 | } |
254 | |
255 | bool KACL::setOthersPermissions(unsigned short v) |
256 | { |
257 | #if HAVE_POSIX_ACL |
258 | permissionsToEntry(entryForTag(d->m_acl, ACL_OTHER), v); |
259 | #else |
260 | Q_UNUSED(v); |
261 | #endif |
262 | return true; |
263 | } |
264 | |
265 | mode_t KACL::basePermissions() const |
266 | { |
267 | mode_t perms(0); |
268 | #if HAVE_POSIX_ACL |
269 | if (ownerPermissions() & ACL_READ) { |
270 | perms |= S_IRUSR; |
271 | } |
272 | if (ownerPermissions() & ACL_WRITE) { |
273 | perms |= S_IWUSR; |
274 | } |
275 | if (ownerPermissions() & ACL_EXECUTE) { |
276 | perms |= S_IXUSR; |
277 | } |
278 | if (owningGroupPermissions() & ACL_READ) { |
279 | perms |= S_IRGRP; |
280 | } |
281 | if (owningGroupPermissions() & ACL_WRITE) { |
282 | perms |= S_IWGRP; |
283 | } |
284 | if (owningGroupPermissions() & ACL_EXECUTE) { |
285 | perms |= S_IXGRP; |
286 | } |
287 | if (othersPermissions() & ACL_READ) { |
288 | perms |= S_IROTH; |
289 | } |
290 | if (othersPermissions() & ACL_WRITE) { |
291 | perms |= S_IWOTH; |
292 | } |
293 | if (othersPermissions() & ACL_EXECUTE) { |
294 | perms |= S_IXOTH; |
295 | } |
296 | #endif |
297 | return perms; |
298 | } |
299 | |
300 | unsigned short KACL::maskPermissions(bool &exists) const |
301 | { |
302 | exists = true; |
303 | #if HAVE_POSIX_ACL |
304 | acl_entry_t entry = entryForTag(d->m_acl, ACL_MASK); |
305 | if (entry == nullptr) { |
306 | exists = false; |
307 | return 0; |
308 | } |
309 | return entryToPermissions(entry); |
310 | #else |
311 | return 0; |
312 | #endif |
313 | } |
314 | |
315 | #if HAVE_POSIX_ACL |
316 | bool KACL::KACLPrivate::setMaskPermissions(unsigned short v) |
317 | { |
318 | acl_entry_t entry = entryForTag(m_acl, ACL_MASK); |
319 | if (entry == nullptr) { |
320 | acl_create_entry(&m_acl, &entry); |
321 | acl_set_tag_type(entry, ACL_MASK); |
322 | } |
323 | permissionsToEntry(entry, v); |
324 | return true; |
325 | } |
326 | #endif |
327 | |
328 | bool KACL::setMaskPermissions(unsigned short v) |
329 | { |
330 | #if HAVE_POSIX_ACL |
331 | return d->setMaskPermissions(v); |
332 | #else |
333 | Q_UNUSED(v); |
334 | return true; |
335 | #endif |
336 | } |
337 | |
338 | #if HAVE_POSIX_ACL |
339 | using unique_ptr_acl_free = std::unique_ptr<void, int (*)(void *)>; |
340 | #endif |
341 | |
342 | // Deal with named users |
343 | unsigned short KACL::namedUserPermissions(const QString &name, bool *exists) const |
344 | { |
345 | #if HAVE_POSIX_ACL |
346 | acl_entry_t entry; |
347 | *exists = false; |
348 | int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); |
349 | while (ret == 1) { |
350 | acl_tag_t currentTag; |
351 | acl_get_tag_type(entry, ¤tTag); |
352 | if (currentTag == ACL_USER) { |
353 | const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); |
354 | const uid_t id = *(static_cast<uid_t *>(idptr.get())); |
355 | if (d->getUserName(id) == name) { |
356 | *exists = true; |
357 | return entryToPermissions(entry); |
358 | } |
359 | } |
360 | ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); |
361 | } |
362 | #else |
363 | Q_UNUSED(name); |
364 | Q_UNUSED(exists); |
365 | #endif |
366 | return 0; |
367 | } |
368 | |
369 | #if HAVE_POSIX_ACL |
370 | bool KACL::KACLPrivate::setNamedUserOrGroupPermissions(const QString &name, unsigned short permissions, acl_tag_t type) |
371 | { |
372 | bool allIsWell = true; |
373 | acl_t newACL = acl_dup(m_acl); |
374 | acl_entry_t entry; |
375 | bool createdNewEntry = false; |
376 | bool found = false; |
377 | int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry); |
378 | while (ret == 1) { |
379 | acl_tag_t currentTag; |
380 | acl_get_tag_type(entry, ¤tTag); |
381 | if (currentTag == type) { |
382 | const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); |
383 | const int id = *(static_cast<int *>(idptr.get())); // We assume that sizeof(uid_t) == sizeof(gid_t) |
384 | const QString entryName = type == ACL_USER ? getUserName(id) : getGroupName(id); |
385 | if (entryName == name) { |
386 | // found him, update |
387 | permissionsToEntry(entry, permissions); |
388 | found = true; |
389 | break; |
390 | } |
391 | } |
392 | ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry); |
393 | } |
394 | if (!found) { |
395 | acl_create_entry(&newACL, &entry); |
396 | acl_set_tag_type(entry, type); |
397 | int id = type == ACL_USER ? getUidForName(name) : getGidForName(name); |
398 | if (id == -1 || acl_set_qualifier(entry, &id) != 0) { |
399 | acl_delete_entry(newACL, entry); |
400 | allIsWell = false; |
401 | } else { |
402 | permissionsToEntry(entry, permissions); |
403 | createdNewEntry = true; |
404 | } |
405 | } |
406 | if (allIsWell && createdNewEntry) { |
407 | // 23.1.1 of 1003.1e states that as soon as there is a named user or |
408 | // named group entry, there needs to be a mask entry as well, so add |
409 | // one, if the user hasn't explicitly set one. |
410 | if (entryForTag(newACL, ACL_MASK) == nullptr) { |
411 | acl_calc_mask(&newACL); |
412 | } |
413 | } |
414 | |
415 | if (!allIsWell || acl_valid(newACL) != 0) { |
416 | acl_free(newACL); |
417 | allIsWell = false; |
418 | } else { |
419 | acl_free(m_acl); |
420 | m_acl = newACL; |
421 | } |
422 | return allIsWell; |
423 | } |
424 | #endif |
425 | |
426 | bool KACL::setNamedUserPermissions(const QString &name, unsigned short permissions) |
427 | { |
428 | #if HAVE_POSIX_ACL |
429 | return d->setNamedUserOrGroupPermissions(name, permissions, ACL_USER); |
430 | #else |
431 | Q_UNUSED(name); |
432 | Q_UNUSED(permissions); |
433 | return true; |
434 | #endif |
435 | } |
436 | |
437 | ACLUserPermissionsList KACL::allUserPermissions() const |
438 | { |
439 | ACLUserPermissionsList list; |
440 | #if HAVE_POSIX_ACL |
441 | acl_entry_t entry; |
442 | int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); |
443 | while (ret == 1) { |
444 | acl_tag_t currentTag; |
445 | acl_get_tag_type(entry, ¤tTag); |
446 | if (currentTag == ACL_USER) { |
447 | const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); |
448 | const uid_t id = *(static_cast<uid_t *>(idptr.get())); |
449 | QString name = d->getUserName(id); |
450 | unsigned short permissions = entryToPermissions(entry); |
451 | ACLUserPermissions pair = qMakePair(name, permissions); |
452 | list.append(pair); |
453 | } |
454 | ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); |
455 | } |
456 | #endif |
457 | return list; |
458 | } |
459 | |
460 | #if HAVE_POSIX_ACL |
461 | bool KACL::KACLPrivate::setAllUsersOrGroups(const QList<QPair<QString, unsigned short>> &list, acl_tag_t type) |
462 | { |
463 | bool allIsWell = true; |
464 | bool atLeastOneUserOrGroup = false; |
465 | |
466 | // make working copy, in case something goes wrong |
467 | acl_t newACL = acl_dup(m_acl); |
468 | acl_entry_t entry; |
469 | |
470 | // clear user entries |
471 | int ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry); |
472 | while (ret == 1) { |
473 | acl_tag_t currentTag; |
474 | acl_get_tag_type(entry, ¤tTag); |
475 | if (currentTag == type) { |
476 | acl_delete_entry(newACL, entry); |
477 | // we have to start from the beginning, the iterator is |
478 | // invalidated, on deletion |
479 | ret = acl_get_entry(newACL, ACL_FIRST_ENTRY, &entry); |
480 | } else { |
481 | ret = acl_get_entry(newACL, ACL_NEXT_ENTRY, &entry); |
482 | } |
483 | } |
484 | |
485 | // now add the entries from the list |
486 | for (const auto &[name, userId] : list) { |
487 | acl_create_entry(&newACL, &entry); |
488 | acl_set_tag_type(entry, type); |
489 | int id = type == ACL_USER ? getUidForName(name) : getGidForName(name); |
490 | if (id == -1 || acl_set_qualifier(entry, &id) != 0) { |
491 | // user or group doesn't exist => error |
492 | acl_delete_entry(newACL, entry); |
493 | allIsWell = false; |
494 | break; |
495 | } else { |
496 | permissionsToEntry(entry, userId); |
497 | atLeastOneUserOrGroup = true; |
498 | } |
499 | } |
500 | |
501 | if (allIsWell && atLeastOneUserOrGroup) { |
502 | // 23.1.1 of 1003.1e states that as soon as there is a named user or |
503 | // named group entry, there needs to be a mask entry as well, so add |
504 | // one, if the user hasn't explicitly set one. |
505 | if (entryForTag(newACL, ACL_MASK) == nullptr) { |
506 | acl_calc_mask(&newACL); |
507 | } |
508 | } |
509 | if (allIsWell && (acl_valid(newACL) == 0)) { |
510 | acl_free(m_acl); |
511 | m_acl = newACL; |
512 | } else { |
513 | acl_free(newACL); |
514 | } |
515 | return allIsWell; |
516 | } |
517 | #endif |
518 | |
519 | bool KACL::setAllUserPermissions(const ACLUserPermissionsList &users) |
520 | { |
521 | #if HAVE_POSIX_ACL |
522 | return d->setAllUsersOrGroups(users, ACL_USER); |
523 | #else |
524 | Q_UNUSED(users); |
525 | return true; |
526 | #endif |
527 | } |
528 | |
529 | // Deal with named groups |
530 | |
531 | unsigned short KACL::namedGroupPermissions(const QString &name, bool *exists) const |
532 | { |
533 | *exists = false; |
534 | #if HAVE_POSIX_ACL |
535 | acl_entry_t entry; |
536 | int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); |
537 | while (ret == 1) { |
538 | acl_tag_t currentTag; |
539 | acl_get_tag_type(entry, ¤tTag); |
540 | if (currentTag == ACL_GROUP) { |
541 | const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); |
542 | const gid_t id = *(static_cast<gid_t *>(idptr.get())); |
543 | if (d->getGroupName(id) == name) { |
544 | *exists = true; |
545 | return entryToPermissions(entry); |
546 | } |
547 | } |
548 | ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); |
549 | } |
550 | #else |
551 | Q_UNUSED(name); |
552 | #endif |
553 | return 0; |
554 | } |
555 | |
556 | bool KACL::setNamedGroupPermissions(const QString &name, unsigned short permissions) |
557 | { |
558 | #if HAVE_POSIX_ACL |
559 | return d->setNamedUserOrGroupPermissions(name, permissions, ACL_GROUP); |
560 | #else |
561 | Q_UNUSED(name); |
562 | Q_UNUSED(permissions); |
563 | return true; |
564 | #endif |
565 | } |
566 | |
567 | ACLGroupPermissionsList KACL::allGroupPermissions() const |
568 | { |
569 | ACLGroupPermissionsList list; |
570 | #if HAVE_POSIX_ACL |
571 | acl_entry_t entry; |
572 | int ret = acl_get_entry(d->m_acl, ACL_FIRST_ENTRY, &entry); |
573 | while (ret == 1) { |
574 | acl_tag_t currentTag; |
575 | acl_get_tag_type(entry, ¤tTag); |
576 | if (currentTag == ACL_GROUP) { |
577 | const unique_ptr_acl_free idptr(acl_get_qualifier(entry), acl_free); |
578 | const gid_t id = *(static_cast<gid_t *>(idptr.get())); |
579 | QString name = d->getGroupName(id); |
580 | unsigned short permissions = entryToPermissions(entry); |
581 | ACLGroupPermissions pair = qMakePair(name, permissions); |
582 | list.append(pair); |
583 | } |
584 | ret = acl_get_entry(d->m_acl, ACL_NEXT_ENTRY, &entry); |
585 | } |
586 | #endif |
587 | return list; |
588 | } |
589 | |
590 | bool KACL::setAllGroupPermissions(const ACLGroupPermissionsList &groups) |
591 | { |
592 | #if HAVE_POSIX_ACL |
593 | return d->setAllUsersOrGroups(groups, ACL_GROUP); |
594 | #else |
595 | Q_UNUSED(groups); |
596 | return true; |
597 | #endif |
598 | } |
599 | |
600 | // from and to string |
601 | |
602 | bool KACL::setACL(const QString &aclStr) |
603 | { |
604 | bool ret = false; |
605 | #if HAVE_POSIX_ACL |
606 | acl_t temp = acl_from_text(aclStr.toLatin1().constData()); |
607 | if (acl_valid(temp) != 0) { |
608 | // TODO errno is set, what to do with it here? |
609 | acl_free(temp); |
610 | } else { |
611 | if (d->m_acl) { |
612 | acl_free(d->m_acl); |
613 | } |
614 | d->m_acl = temp; |
615 | ret = true; |
616 | } |
617 | #else |
618 | Q_UNUSED(aclStr); |
619 | #endif |
620 | return ret; |
621 | } |
622 | |
623 | QString KACL::asString() const |
624 | { |
625 | #if HAVE_POSIX_ACL |
626 | ssize_t size = 0; |
627 | char *txt = acl_to_text(d->m_acl, &size); |
628 | const QString ret = QString::fromLatin1(txt, size); |
629 | acl_free(txt); |
630 | return ret; |
631 | #else |
632 | return QString(); |
633 | #endif |
634 | } |
635 | |
636 | // helpers |
637 | |
638 | #if HAVE_POSIX_ACL |
639 | QString KACL::KACLPrivate::getUserName(uid_t uid) const |
640 | { |
641 | auto it = m_usercache.find(uid); |
642 | if (it == m_usercache.end()) { |
643 | struct passwd *user = getpwuid(uid); |
644 | if (user) { |
645 | it = m_usercache.insert(uid, QString::fromLatin1(user->pw_name)); |
646 | } else { |
647 | return QString::number(uid); |
648 | } |
649 | } |
650 | return *it; |
651 | } |
652 | |
653 | QString KACL::KACLPrivate::getGroupName(gid_t gid) const |
654 | { |
655 | auto it = m_groupcache.find(gid); |
656 | if (it == m_groupcache.end()) { |
657 | struct group *grp = getgrgid(gid); |
658 | if (grp) { |
659 | it = m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name)); |
660 | } else { |
661 | return QString::number(gid); |
662 | } |
663 | } |
664 | return *it; |
665 | } |
666 | #endif |
667 | |
668 | void KACL::virtual_hook(int, void *) |
669 | { |
670 | /*BASE::virtual_hook( id, data );*/ |
671 | } |
672 | |
673 | QDataStream &operator<<(QDataStream &s, const KACL &a) |
674 | { |
675 | s << a.asString(); |
676 | return s; |
677 | } |
678 | |
679 | QDataStream &operator>>(QDataStream &s, KACL &a) |
680 | { |
681 | QString str; |
682 | s >> str; |
683 | a.setACL(str); |
684 | return s; |
685 | } |
686 | |