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