From f948fe25f42fa6dfcc4088a53d7c31b9335653e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomi=20Lepp=C3=A4nen?= Date: Fri, 24 Apr 2020 13:12:11 +0300 Subject: [PATCH] [nemo-systemsettings] Add alone property to UserInfo. Contributes to JB#49696 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add alone property that can be used to determine if user is the only user on device. It uses tristated value on the background so that if it isn't needed, it's not checked. It is also wired up to the file system watcher so that it gets updated if groups change. Signed-off-by: Tomi Leppänen --- src/userinfo.cpp | 103 ++++++++++++++++++++++++++++++++++++++++++----- src/userinfo.h | 3 ++ src/userinfo_p.h | 10 +++++ 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/userinfo.cpp b/src/userinfo.cpp index 7502488..994be6c 100644 --- a/src/userinfo.cpp +++ b/src/userinfo.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ namespace { const auto UserDatabaseFile = QStringLiteral("/etc/passwd"); +const auto GroupDatabaseFile = QStringLiteral("/etc/group"); enum SpecialIds : uid_t { DeviceOwnerId = 100000, @@ -70,6 +72,7 @@ UserInfoPrivate::UserInfoPrivate() : m_uid(InvalidId) , m_loggedIn(false) , m_watcher(nullptr) + , m_alone(Unknown) { } @@ -82,6 +85,7 @@ UserInfoPrivate::UserInfoPrivate(struct passwd *pwd) // counted as they don't have seats. , m_loggedIn(sd_uid_is_on_seat(m_uid, 1, "seat0") > 0) , m_watcher(nullptr) + , m_alone(Unknown) { } @@ -120,6 +124,56 @@ void UserInfoPrivate::set(struct passwd *pwd) } } +bool UserInfoPrivate::alone() +{ + if (m_alone == Unknown) + updateAlone(true); + return m_alone == Yes; +} + +void UserInfoPrivate::updateAlone(bool force) +{ + if (!force && m_alone == Unknown) { + // Skip if the value is not needed and the check is not forced + return; + } + + Tristated alone = Yes; + + if (m_uid != InvalidId && m_uid != UnknownCurrentUserId && m_uid != DeviceOwnerId) { + // There must be at least one other user besides device owner + // if the uid is valid and known and it's not device owner + alone = No; + } else { + // Can not determine from uid, check users group + errno = 0; + struct group *grp = getgrnam("users"); + if (!grp) { + qCWarning(lcUsersLog) << "Could not read users group:" << strerror(errno); + // Guessing that user is probably alone + } else { + for (int i = 0; grp->gr_mem[i] != nullptr; ++i) { + struct passwd *pwd = getpwnam(grp->gr_mem[i]); + if (pwd && pwd->pw_uid != DeviceOwnerId) { + // Found someone that's not device owner + alone = No; + break; + } + // pwd must not be free'd + } + // grp must not be free'd + } + } + + if (m_alone != alone) { + m_alone = alone; + if (!force) { + // Emit only if something needed the value already, i.e. it was known + emit aloneChanged(); + } + } +} + /** * Construct UserInfo for the current user * @@ -326,11 +380,24 @@ bool UserInfo::updateCurrent() return false; } +/** + * Returns true if there is only one user on the device + */ +bool UserInfo::alone() +{ + Q_D(UserInfo); + return d->alone(); +} + +/** + * Resets object reloading all information + */ void UserInfo::reset() { Q_D(UserInfo); d->set((isValid()) ? getpwuid(d->m_uid) : nullptr); updateCurrent(); + d->updateAlone(); } void UserInfo::replace(QSharedPointer other) @@ -360,6 +427,10 @@ void UserInfo::replace(QSharedPointer other) if (old->m_watcher) watchForChanges(); + // If alone value was known, ensure that new d_ptr also knows it + if (old->m_alone != UserInfoPrivate::Unknown && old->alone() != d_ptr->alone()) + emit aloneChanged(); + connectSignals(); } @@ -394,29 +465,39 @@ void UserInfo::connectSignals() connect(d_ptr.data(), &UserInfoPrivate::nameChanged, this, &UserInfo::nameChanged); connect(d_ptr.data(), &UserInfoPrivate::uidChanged, this, &UserInfo::uidChanged); connect(d_ptr.data(), &UserInfoPrivate::currentChanged, this, &UserInfo::currentChanged); + connect(d_ptr.data(), &UserInfoPrivate::aloneChanged, this, &UserInfo::aloneChanged); } void UserInfo::watchForChanges() { Q_D(UserInfo); d->m_watcher = new QFileSystemWatcher(this); - if (!d->m_watcher->addPath(UserDatabaseFile)) { - qCWarning(lcUsersLog) << "Could not watch for changes in user database"; + QStringList missing = d->m_watcher->addPaths(QStringList() << UserDatabaseFile << GroupDatabaseFile); + if (missing.count() == 2) { + qCWarning(lcUsersLog) << "Could not watch for changes in user or group database"; delete d->m_watcher; d->m_watcher = nullptr; + } else if (missing.count() > 0) { + qCWarning(lcUsersLog) << "Could not watch for changes in" << missing; } else { - connect(d->m_watcher, &QFileSystemWatcher::fileChanged, this, [this] { + connect(d->m_watcher, &QFileSystemWatcher::fileChanged, this, [this] (const QString &path) { Q_D(UserInfo); - if (QFile::exists(UserDatabaseFile)) { - // Database updated, reset - qCDebug(lcUsersLog) << "Reseting model because user database changed"; - reset(); + if (QFile::exists(path)) { + if (path == UserDatabaseFile) { + // User database updated, reset model + qCDebug(lcUsersLog) << UserDatabaseFile << "changed, reseting model"; + reset(); + } else if (d->m_alone != UserInfoPrivate::Unknown) { // && path == GroupDatabaseFile + // Group database updated, update alone status + qCDebug(lcUsersLog) << GroupDatabaseFile << "changed, checking alone status again"; + d->updateAlone(); + } } - if (!d->m_watcher->files().contains(UserDatabaseFile)) { - if (QFile::exists(UserDatabaseFile) && d->m_watcher->addPath(UserDatabaseFile)) { - qCDebug(lcUsersLog) << "Re-watching user database for changes"; + if (!d->m_watcher->files().contains(path)) { + if (QFile::exists(path) && d->m_watcher->addPath(path)) { + qCDebug(lcUsersLog) << "Re-watching" << path << "for changes"; } else { - qCWarning(lcUsersLog) << "Stopped watching user database for changes"; + qCWarning(lcUsersLog) << "Stopped watching" << path << "for changes"; } } }); diff --git a/src/userinfo.h b/src/userinfo.h index 756ae2a..0f8cf3e 100644 --- a/src/userinfo.h +++ b/src/userinfo.h @@ -52,6 +52,7 @@ class SYSTEMSETTINGS_EXPORT UserInfo: public QObject Q_PROPERTY(UserType type READ type CONSTANT) Q_PROPERTY(int uid READ uid WRITE setUid NOTIFY uidChanged) Q_PROPERTY(bool current READ current NOTIFY currentChanged) + Q_PROPERTY(bool alone READ alone NOTIFY aloneChanged) friend class UserModel; @@ -77,6 +78,7 @@ class SYSTEMSETTINGS_EXPORT UserInfo: public QObject int uid() const; void setUid(int uid); bool current() const; + bool alone(); Q_INVOKABLE void reset(); @@ -90,6 +92,7 @@ class SYSTEMSETTINGS_EXPORT UserInfo: public QObject void nameChanged(); void uidChanged(); void currentChanged(); + void aloneChanged(); private: explicit UserInfo(int uid); diff --git a/src/userinfo_p.h b/src/userinfo_p.h index 5b33ce1..4f95773 100644 --- a/src/userinfo_p.h +++ b/src/userinfo_p.h @@ -49,14 +49,23 @@ class UserInfoPrivate : public QObject UserInfoPrivate(struct passwd *pwd); ~UserInfoPrivate(); + enum Tristated { + Yes = 1, + No = 0, + Unknown = -1 + }; + uid_t m_uid; QString m_username; QString m_name; bool m_loggedIn; static QWeakPointer s_current; QFileSystemWatcher *m_watcher; + Tristated m_alone; void set(struct passwd *pwd); + bool alone(); + void updateAlone(bool force = false); signals: void displayNameChanged(); @@ -64,6 +73,7 @@ class UserInfoPrivate : public QObject void nameChanged(); void uidChanged(); void currentChanged(); + void aloneChanged(); }; #endif /* USERINFOPRIVATE_H */