]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/shared-data-manager.c
dbdc30101c46acc8f8de7f30427e8881aea8d0d9
[sojka/lightdm.git] / src / shared-data-manager.c
1 /*
2  * Copyright (C) 2014 Canonical, Ltd
3  * Author: Michael Terry <michael.terry@canonical.com>
4  * 
5  * This program is free software: you can redistribute it and/or modify it under
6  * the terms of the GNU General Public License as published by the Free Software
7  * Foundation, either version 3 of the License, or (at your option) any later
8  * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
9  * license.
10  */
11
12 #include <config.h>
13 #include <gio/gio.h>
14 #include <pwd.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17
18 #include "configuration.h"
19 #include "shared-data-manager.h"
20 #include "user-list.h"
21
22 #define NUM_ENUMERATION_FILES 100
23
24 struct SharedDataManagerPrivate
25 {
26     guint32 greeter_uid;
27     guint32 greeter_gid;
28     guint num_setup_users;
29     GHashTable *starting_dirs;
30 };
31
32 struct OwnerInfo
33 {
34     SharedDataManager *manager;
35     guint32 uid;
36 };
37
38 G_DEFINE_TYPE (SharedDataManager, shared_data_manager, G_TYPE_OBJECT);
39
40 SharedDataManager *
41 shared_data_manager_new (void)
42 {
43     return g_object_new (SHARED_DATA_MANAGER_TYPE, NULL);
44 }
45
46 static void
47 delete_unused_user (gpointer key, gpointer value, gpointer user_data)
48 {
49     const gchar *user = (const gchar *)key;
50     GError *error = NULL;
51
52     /* Listen, the rest of this file is nice async glib code and all, but
53        for this operation, we just need a fire and forget rm -rf.  Since
54        recursively deleting in GIO is a huge pain in the butt, we'll just drop
55        to shell for this. */
56
57     gchar *path = g_build_filename (USERS_DIR, user, NULL);
58     gchar *quoted_path = g_shell_quote (path);
59     gchar *cmd = g_strdup_printf ("/bin/rm -rf %s", quoted_path);
60
61     if (!g_spawn_command_line_async (cmd, &error))
62     {
63         g_warning ("Could not delete unused user data directory %s: %s", path, error->message);
64         g_error_free (error);
65     }
66
67     g_free (cmd);
68     g_free (quoted_path);
69     g_free (path);
70 }
71
72 static void
73 chown_user_dir_cb (GObject *object, GAsyncResult *res, gpointer user_data)
74 {
75     GFile *file = G_FILE (object);
76     GFileInfo *info = NULL;
77     GError *error = NULL;
78
79     if (!g_file_set_attributes_finish (file, res, &info, &error))
80     {
81         gchar *path = g_file_get_path (file);
82         g_warning ("Could not chown user data directory %s: %s",
83                    path, error->message);
84         g_free (path);
85         g_error_free (error);
86     }
87
88     if (info)
89         g_object_unref (info);
90 }
91
92 static void
93 make_user_dir_cb (GObject *object, GAsyncResult *res, gpointer user_data)
94 {
95     GFile *file = G_FILE (object);
96     struct OwnerInfo *owner = (struct OwnerInfo *)user_data;
97     GError *error = NULL;
98
99     if (!g_file_make_directory_finish (file, res, &error))
100     {
101         if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
102         {
103             gchar *path = g_file_get_path (file);
104             g_warning ("Could not create user data directory %s: %s",
105                        path, error->message);
106             g_free (path);
107             g_error_free (error);
108             owner->manager->priv->num_setup_users--;
109             g_object_unref (owner->manager);
110             g_free (owner);
111             return;
112         }
113         g_error_free (error);
114     }
115
116     /* Even if the directory already exists, we want to re-affirm the owners
117        because the greeter gid is configuration based and may change between
118        runs. */
119     GFileInfo *info = g_file_info_new ();
120     g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID,
121                                       owner->uid);
122     g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID,
123                                       owner->manager->priv->greeter_gid);
124     g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, 0770);
125     g_file_set_attributes_async (file, info, G_FILE_QUERY_INFO_NONE,
126                                  G_PRIORITY_DEFAULT, NULL,
127                                  chown_user_dir_cb, NULL);
128
129     /* If we're the last user dir to be set up, delete unused user dirs */
130     owner->manager->priv->num_setup_users--;
131     if (owner->manager->priv->starting_dirs != NULL &&
132         owner->manager->priv->num_setup_users == 0)
133     {
134         g_hash_table_foreach (owner->manager->priv->starting_dirs,
135                               delete_unused_user, owner->manager);
136         g_hash_table_destroy (owner->manager->priv->starting_dirs);
137         owner->manager->priv->starting_dirs = NULL;
138     }
139
140     g_object_unref (owner->manager);
141     g_free (owner);
142 }
143
144 static void
145 setup_user_dir (SharedDataManager *manager, guint32 uid)
146 {
147     struct OwnerInfo *owner = g_malloc (sizeof (struct OwnerInfo));
148     owner->manager = g_object_ref (manager);
149     owner->uid = uid;
150
151     gchar *uidstr = g_strdup_printf ("%u", uid);
152     gchar *path = g_build_filename (USERS_DIR, uidstr, NULL);
153     GFile *file = g_file_new_for_path (path);
154     g_free (path);
155
156     manager->priv->num_setup_users++;
157     if (manager->priv->starting_dirs != NULL)
158         g_hash_table_remove (manager->priv->starting_dirs, uidstr);
159     g_free (uidstr);
160
161     g_file_make_directory_async (file, G_PRIORITY_DEFAULT, NULL,
162                                  make_user_dir_cb, owner);
163
164     g_object_unref (file);
165 }
166
167 static void
168 next_user_dirs_cb (GObject *object, GAsyncResult *res, gpointer user_data)
169 {
170     GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object);
171     SharedDataManager *manager = SHARED_DATA_MANAGER (user_data);
172     GList *link;
173     GError *error = NULL;
174
175     GList *files = g_file_enumerator_next_files_finish (enumerator, res,
176                                                         &error);
177     if (error != NULL)
178     {
179         g_warning ("Could not enumerate user data directory %s: %s",
180                    USERS_DIR, error->message);
181         g_error_free (error);
182         g_object_unref (manager);
183         return;
184     }
185
186     for (link = files; link; link = link->next)
187     {
188         GFileInfo *info = link->data;
189         g_hash_table_insert (manager->priv->starting_dirs,
190                              g_strdup (g_file_info_get_name (info)), NULL);
191     }
192
193     if (files != NULL)
194     {
195         g_list_free_full (files, g_object_unref);
196         g_file_enumerator_next_files_async (enumerator, NUM_ENUMERATION_FILES,
197                                             G_PRIORITY_DEFAULT, NULL,
198                                             next_user_dirs_cb, manager);
199     }
200     else
201     {
202         // We've finally assembled all the initial directories.  Now let's
203         // iterate the current users and set them each up.  As we go, we'll
204         // remove the users from the starting_dirs hash and thus see which
205         // users are obsolete.
206         GList *users = common_user_list_get_users (common_user_list_get_instance ());
207         for (link = users; link; link = link->next)
208         {
209             CommonUser *user = link->data;
210             setup_user_dir (manager, common_user_get_uid (user));
211         }
212         // Also set up our own greeter dir, so it has a place to dump its own files
213         // (imagine it holding some large files temporarily before shunting them
214         // to the next user to log in's specific directory).
215         setup_user_dir (manager, manager->priv->greeter_uid);
216         g_object_unref (manager);
217     }
218 }
219
220 static void
221 list_user_dirs_cb (GObject *object, GAsyncResult *res, gpointer user_data)
222 {
223     GFile *file = G_FILE (object);
224     SharedDataManager *manager = SHARED_DATA_MANAGER (user_data);
225     GFileEnumerator *enumerator;
226     GError *error = NULL;
227
228     enumerator = g_file_enumerate_children_finish (file, res, &error);
229     if (enumerator == NULL)
230     {
231         g_warning ("Could not enumerate user data directory %s: %s",
232                    USERS_DIR, error->message);
233         g_error_free (error);
234         g_object_unref (manager);
235         return;
236     }
237
238     manager->priv->starting_dirs = g_hash_table_new_full (g_str_hash,
239                                                           g_str_equal,
240                                                           g_free, NULL);
241     g_file_enumerator_next_files_async (enumerator, NUM_ENUMERATION_FILES,
242                                         G_PRIORITY_DEFAULT, NULL,
243                                         next_user_dirs_cb, manager);
244 }
245
246 static void
247 user_added_cb (CommonUserList *list, CommonUser *user,
248                SharedDataManager *manager)
249 {
250     setup_user_dir (manager, common_user_get_uid (user));
251 }
252
253 static void
254 user_removed_cb (CommonUserList *list, CommonUser *user,
255                  SharedDataManager *manager)
256 {
257     gchar *uid = g_strdup_printf ("%u", common_user_get_uid (user));
258     delete_unused_user (uid, NULL, manager);
259     g_free (uid);
260 }
261
262 static void
263 shared_data_manager_init (SharedDataManager *manager)
264 {
265     manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, SHARED_DATA_MANAGER_TYPE, SharedDataManagerPrivate);
266
267     // Grab current greeter-user gid
268     gchar *greeter_user;
269     struct passwd *greeter_entry;
270     greeter_user = config_get_string (config_get_instance (), "LightDM", "greeter-user");
271     greeter_entry = getpwnam (greeter_user);
272     if (greeter_entry)
273     {
274         manager->priv->greeter_uid = greeter_entry->pw_uid;
275         manager->priv->greeter_gid = greeter_entry->pw_gid;
276     }
277     g_free (greeter_user);
278
279     /* Grab list of all current directories, so we know if any exist that we
280        no longer need. */
281     GFile *file = g_file_new_for_path (USERS_DIR);
282     g_file_enumerate_children_async (file, G_FILE_ATTRIBUTE_STANDARD_NAME,
283                                      G_FILE_QUERY_INFO_NONE,
284                                      G_PRIORITY_DEFAULT, NULL,
285                                      list_user_dirs_cb, g_object_ref (manager));
286     g_object_unref (file);
287
288     /* And listen for user changes.  The chance of a race with the above
289        initial setup is so tiny, it's not worth worrying about. */
290     g_signal_connect (common_user_list_get_instance (), "user-added",
291                       G_CALLBACK (user_added_cb), manager);
292     g_signal_connect (common_user_list_get_instance (), "user-removed",
293                       G_CALLBACK (user_removed_cb), manager);
294 }
295
296 static void
297 shared_data_manager_dispose (GObject *object)
298 {
299     SharedDataManager *self = SHARED_DATA_MANAGER (object);
300
301     /* Should also cancel outstanding GIO operations, but whatever, let them
302        do their thing. */
303
304     g_signal_handlers_disconnect_by_data (common_user_list_get_instance (),
305                                           self);
306
307     G_OBJECT_CLASS (shared_data_manager_parent_class)->dispose (object);
308 }
309
310 static void
311 shared_data_manager_finalize (GObject *object)
312 {
313     SharedDataManager *self = SHARED_DATA_MANAGER (object);
314
315     if (self->priv->starting_dirs)
316         g_hash_table_destroy (self->priv->starting_dirs);
317
318     G_OBJECT_CLASS (shared_data_manager_parent_class)->finalize (object);
319 }
320
321 static void
322 shared_data_manager_class_init (SharedDataManagerClass *klass)
323 {
324     GObjectClass *object_class = G_OBJECT_CLASS (klass);
325
326     object_class->dispose = shared_data_manager_dispose;
327     object_class->finalize = shared_data_manager_finalize;
328
329     g_type_class_add_private (klass, sizeof (SharedDataManagerPrivate));
330 }