Reimplement `subsession_get_user_list` by performing subsession 03/284403/13
authorAdam Michalski <a.michalski2@partner.samsung.com>
Wed, 16 Nov 2022 16:50:07 +0000 (17:50 +0100)
committerAdam Michalski <a.michalski2@partner.samsung.com>
Wed, 23 Nov 2022 13:13:47 +0000 (14:13 +0100)
directory interrogation directly whenever possible

Co-author: Michal Bloch <m.bloch@samsung.com>

Change-Id: I6b02fcf590f75fdb8f60db0a328d3ff5409f10cb

src/library/src/lib.c
tests/api_tests/test_api_get_user_list.cpp

index 94b4459..6b494e0 100644 (file)
 #include <gio/gio.h>
 #include <tizen.h>
 #include <ctype.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <dirent.h>
 
 #undef LOG_TAG
 #define LOG_TAG "LIBSESSIOND"
@@ -34,6 +38,7 @@
 #include "sessiond-internal.h"
 
 const int libsessiond_default_timeout = 20000;
+static const char subsession_subdir[] = "/subsession";
 
 static session_connection_data_t session_connection_data = {
        .connection = NULL,
@@ -843,40 +848,147 @@ EXPORT_API int subsession_event_wait_done(subsession_event_info info)
        return SUBSESSION_ERROR_INVALID_PARAMETER;
 }
 
-EXPORT_API int subsession_get_user_list(int session_uid, subsession_user_t **user_list, int *user_count)
+static inline void free_ptr(const void *ptr)
 {
-       return_if(session_uid_is_not_valid(session_uid, user_count_ptr_is_null(user_count)))
+       free(*(void * const *)ptr);
+}
 
+static int query_dir_via_dbus(int session_uid, subsession_user_t **const user_list, int *const user_count)
+{
        g_autoptr(GVariant) out = NULL;
-       int ret = method_call_sync(dbus_method_call.GetUserList,
-                       g_variant_new("(i)", session_uid),
-                       G_VARIANT_TYPE("(as)"),
-                       &out
-                 );
-
+       const int ret = method_call_sync(dbus_method_call.GetUserList,
+               g_variant_new("(i)", session_uid),
+               G_VARIANT_TYPE("(as)"),
+               &out
+       );
        if (ret != SUBSESSION_ERROR_NONE)
                return ret;
 
        g_autoptr(GVariant) array = g_variant_get_child_value(out, 0);
+       if (!array)
+               return SUBSESSION_ERROR_OUT_OF_MEMORY;
+
        gsize elem_no = 0;
-       g_autofree const char** data = g_variant_get_strv(array, &elem_no);
+       g_autofree const char **data = g_variant_get_strv(array, &elem_no);
 
        if (user_list != NULL && elem_no > 0) {
-               subsession_user_t *list = calloc(elem_no, sizeof(subsession_user_t));
-               if (list == NULL) {
+               subsession_user_t *const list = calloc(elem_no, sizeof *list);
+               if (list == NULL)
                        return SUBSESSION_ERROR_OUT_OF_MEMORY;
-               }
+
                *user_list = list;
 
-               for (gsize i = 0; i < elem_no; ++i) {
+               for (gsize i = 0; i < elem_no; ++i)
                        subsession_user_copy((*user_list)[i], data[i]);
-               }
        }
 
        *user_count = elem_no;
        return SUBSESSION_ERROR_NONE;
 }
 
+/* A twin implementation of the same logic in the daemon can be found
+ * in the `src/service/src/fs_helpers.cpp` file,
+ * `get_user_list(const int session_uid)` function
+ */
+static int query_dir_via_readdir(DIR *const dir_ptr, subsession_user_t **const user_list, int *const user_count)
+{
+       struct dirent *entry_ptr;
+       size_t elems = 1; // because of SUBSESSION_INITIAL_SID
+
+       if (dir_ptr != NULL)
+               while ((entry_ptr = readdir(dir_ptr)) != NULL)
+                       if (!error_on_bad_user_id(entry_ptr->d_name))
+                               ++elems;
+
+       *user_count = 1;
+
+       subsession_user_t *const list = calloc(elems, sizeof *list);
+       if (list == NULL)
+               return SUBSESSION_ERROR_OUT_OF_MEMORY;
+       if (user_list != NULL) {
+               *user_list = list;
+               subsession_user_copy((*user_list)[0], SUBSESSION_INITIAL_SID);
+       }
+
+       if (elems == 1)
+               return SUBSESSION_ERROR_NONE;
+
+       rewinddir(dir_ptr);
+
+       size_t dir_idx = 1;
+       while ((entry_ptr = readdir(dir_ptr)) != NULL && dir_idx < elems) {
+               if (!error_on_bad_user_id(entry_ptr->d_name)) {
+                       if (user_list != NULL)
+                               subsession_user_copy((*user_list)[dir_idx], entry_ptr->d_name);
+                       dir_idx++;
+               }
+       }
+
+       /* During the second iteration it's possible to get a different number
+        * of directories than in the first one and - in case of less - we'll
+        * eventually end up with some empty strings ("\0"s) in the `user_list`.
+        * This of course is undesirable. Therefore, we set `user_count` to
+        * the number of read directories during the second iteration.
+        */
+       *user_count = dir_idx;
+
+       return SUBSESSION_ERROR_NONE;
+}
+
+EXPORT_API int subsession_get_user_list(int session_uid, subsession_user_t **user_list, int *user_count)
+{
+       return_if(session_uid_is_not_valid(session_uid, user_count_ptr_is_null(user_count)))
+
+       const long int max_buf_len = sysconf(_SC_GETPW_R_SIZE_MAX);
+       if (max_buf_len <= 0)
+               return SUBSESSION_ERROR_IO_ERROR;
+
+       __attribute__((cleanup(free_ptr))) char *const pw_buf = (char *)malloc(max_buf_len);
+       if (pw_buf == NULL)
+               return SUBSESSION_ERROR_OUT_OF_MEMORY;
+
+       struct passwd pass_buf, *pass_ptr;
+       getpwuid_r(session_uid, &pass_buf, pw_buf, max_buf_len, &pass_ptr);
+       if (!pass_ptr)
+               return SUBSESSION_ERROR_IO_ERROR;
+
+       const size_t home_dir_len = strlen(pass_ptr->pw_dir);
+       const size_t main_dir_len = home_dir_len + sizeof subsession_subdir;
+       __attribute__((cleanup(free_ptr))) char *main_dir = (char*)malloc(main_dir_len);
+       if (main_dir == NULL)
+               return SUBSESSION_ERROR_OUT_OF_MEMORY;
+
+       memcpy(main_dir, pass_ptr->pw_dir, main_dir_len - 1);
+       memcpy(main_dir + home_dir_len, subsession_subdir, main_dir_len - home_dir_len);
+
+       DIR *const dir_ptr = opendir(main_dir);
+       if (dir_ptr == NULL) {
+               /* The DBus service has permissions to access the dir.
+                * Note that it does the same thing we do, but it's
+                * better to attempt to do it locally because that's
+                * faster and getting the user list is often done
+                * in time-sensitive contexts (such as at boot). */
+               if (errno == EACCES)
+                       return query_dir_via_dbus(session_uid, user_list, user_count);
+
+               /* ENOENT here means we have permission to access the dir,
+                * but the `subsession` subdirectory does not exist. This in
+                * turn means that there are currently no subsessions in the
+                * system for the queried `session_uid` and this case will be
+                * handled later by the `query_dir_via_readdir` function.
+                *
+                * Any other system error here means that input parameters are
+                * invalid.
+                */
+               if (errno != ENOENT)
+                       return SUBSESSION_ERROR_INVALID_PARAMETER;
+       }
+
+       const int ret = query_dir_via_readdir(dir_ptr, user_list, user_count);
+       closedir(dir_ptr);
+       return ret;
+}
+
 EXPORT_API int subsession_get_current_user(int session_uid, subsession_user_t user)
 {
        return_if(session_uid_is_not_valid(session_uid, current_user_ptr_is_null(user)))
index eebcf39..3351bfa 100644 (file)
@@ -28,8 +28,14 @@ struct get_user {
 
 void summarize_results_for_counted_only_get_user_list(tgl_ &data, int expected_count_users) {
 
-       std::cout << head_ << "Check if got users number ( " <<  expected_count_users << " ) is same as expected ( "<< data.user_count << " )."<< std::endl;
-       EXPECT_EQ(expected_count_users, data.user_count);
+       /* Since the calls to `subsession_add_user` in this test are asynchronous,
+        * there is no guarantee that adding users will actually be completed
+        * before the call to `subsession_get_user_list`. That's why we expect
+        * the actual number of users returned by the latter call to be less or
+        * equal than the declarative size of `expected_users` array.
+        */
+       std::cout << head_ << "Check if got users number ( " << data.user_count << " ) is less or equal than expected ( " << expected_count_users << " )." << std::endl;
+       EXPECT_TRUE(data.user_count <= expected_count_users);
 
        std::cout << head_ << "Check if got users list is not filled" << std::endl;
        EXPECT_EQ(true, data.user_list == NULL);
@@ -38,8 +44,9 @@ void summarize_results_for_counted_only_get_user_list(tgl_ &data, int expected_c
 template<typename T>
 void summarize_results_for_get_user_list(tgl_ &data, T expected_users) {
 
-       std::cout << head_ << "Check if got users number ( " << expected_users.size() << " ) is same as expected ( "<< data.user_count << " )."<< std::endl;
-       EXPECT_EQ(expected_users.size(), data.user_count);
+       // See the remark in the above function.
+       std::cout << head_ << "Check if got users number ( " << data.user_count << " ) is less or equal than expected ( " << expected_users.size() << " )." << std::endl;
+       EXPECT_TRUE(data.user_count <= expected_users.size());
 
        if(expected_users.size() != data.user_count) {
                return;