]> git.proxmox.com Git - mirror_lxcfs.git/blob - cgmanager.c
remove stale comments, update some
[mirror_lxcfs.git] / cgmanager.c
1 /*
2 * Copyright © 2015 Canonical Limited
3 *
4 * Authors:
5 * Serge Hallyn <serge.hallyn@ubuntu.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <dirent.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <pthread.h>
32 #include <grp.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #include <sys/inotify.h>
37 #include <sys/mount.h>
38 #include <sys/wait.h>
39 #include <netinet/in.h>
40 #include <net/if.h>
41 #include <stdbool.h>
42 #include "cgmanager.h"
43 #include <assert.h>
44
45 #include <glib.h>
46 #include <gio/gio.h>
47
48 #define CGM_DBUS_ADDRESS "unix:path=/sys/fs/cgroup/cgmanager/sock"
49 #define CGM_REQUIRED_VERSION 9 // we need list_keys
50
51 static GDBusConnection *cgroup_manager = NULL;
52
53 static pthread_mutex_t cgm_mutex = PTHREAD_MUTEX_INITIALIZER;
54
55 static void lock_mutex(pthread_mutex_t *l)
56 {
57 int ret;
58 if ((ret = pthread_mutex_lock(l)) != 0) {
59 fprintf(stderr, "pthread_mutex_lock returned:%d %s\n", ret, strerror(ret));
60 exit(1);
61 }
62 }
63
64 static void unlock_mutex(pthread_mutex_t *l)
65 {
66 int ret;
67 if ((ret = pthread_mutex_unlock(l)) != 0) {
68 fprintf(stderr, "pthread_mutex_unlock returned:%d %s\n", ret, strerror(ret));
69 exit(1);
70 }
71 }
72
73 void cgm_lock(void)
74 {
75 lock_mutex(&cgm_mutex);
76 }
77
78 void cgm_unlock(void)
79 {
80 unlock_mutex(&cgm_mutex);
81 }
82
83 /* only called on exit, no need to lock */
84 void cgm_dbus_disconnect(void)
85 {
86 GError *error = NULL;
87
88 if (!cgroup_manager)
89 return;
90
91 if (!g_dbus_connection_flush_sync(cgroup_manager, NULL, &error)) {
92 g_warning("failed to flush connection: %s."
93 "Use G_DBUS_DEBUG=message for more info.", error->message);
94 g_error_free(error);
95 }
96 if (!g_dbus_connection_close_sync(cgroup_manager, NULL, &error)) {
97 g_warning("failed to close connection: %s."
98 "Use G_DBUS_DEBUG=message for more info.", error->message);
99 g_error_free(error);
100 }
101 g_object_unref(cgroup_manager);
102 cgroup_manager = NULL;
103 }
104
105 bool cgm_dbus_connect(void)
106 {
107 GDBusConnection *connection;
108 GVariant *reply;
109 GVariant *version;
110 GError *error = NULL;
111
112 // fastpath - don't lock if we have the manager
113 if (cgroup_manager && !g_dbus_connection_is_closed(cgroup_manager))
114 return true;
115
116 cgm_lock();
117
118 // TODO - do we want to add some limit to nretries?
119 retry:
120 if (cgroup_manager) {
121 if (!g_dbus_connection_is_closed(cgroup_manager)) {
122 // someone else reconnected us
123 cgm_unlock();
124 return true;
125 }
126 fprintf(stderr, "cgmanager connection was closed\n");
127 g_object_unref(cgroup_manager);
128 cgroup_manager = NULL;
129 }
130
131 connection = g_dbus_connection_new_for_address_sync (CGM_DBUS_ADDRESS,
132 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
133 NULL, NULL, &error);
134 if (!connection) {
135 g_warning("Could not connect to cgmanager: %s\n"
136 "Use G_DBUS_DEBUG=message for more info.", error->message);
137 g_error_free(error);
138 error = NULL;
139 fprintf(stderr, "Retrying...\n");
140 sleep(1);
141 goto retry;
142 }
143
144 reply = g_dbus_connection_call_sync (connection, NULL, "/org/linuxcontainers/cgmanager",
145 "org.freedesktop.DBus.Properties", "Get",
146 g_variant_new ("(ss)", "org.linuxcontainers.cgmanager0_0", "api_version"),
147 G_VARIANT_TYPE ("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
148 if (!reply)
149 {
150 g_warning("Failed to get cgmanager api version: %s\n"
151 "Use G_DBUS_DEBUG=message for more info.", error->message);
152 g_error_free(error);
153 g_object_unref (connection);
154 cgm_unlock();
155 return false;
156 }
157 g_variant_get (reply, "(v)", &version);
158 g_variant_unref (reply);
159 if (!g_variant_is_of_type (version, G_VARIANT_TYPE_INT32) || g_variant_get_int32 (version) < CGM_REQUIRED_VERSION)
160 {
161 g_warning("Cgmanager does not meet minimal API version");
162 g_object_unref (connection);
163 g_variant_unref (version);
164 cgm_unlock();
165 return false; }
166 g_variant_unref (version);
167 cgroup_manager = connection;
168
169 cgm_unlock();
170 return true;
171 }
172
173 static bool cgcall(const gchar *method_name, GVariant *parameters,
174 const GVariantType *reply_type, GVariant **reply)
175 {
176 GVariant *my_reply = NULL;
177 GError *error = NULL;
178
179 if (!cgm_dbus_connect()) {
180 g_warning("Error: unable to connect to cgmanager");
181 return false;
182 }
183
184 if (!reply)
185 reply = &my_reply;
186 /* We do this sync because we need to ensure that the calls finish
187 * before we return to _our_ caller saying that this is done.
188 */
189 *reply = g_dbus_connection_call_sync (cgroup_manager, NULL, "/org/linuxcontainers/cgmanager",
190 "org.linuxcontainers.cgmanager0_0", method_name,
191 parameters, reply_type, G_DBUS_CALL_FLAGS_NONE,
192 -1, NULL, &error);
193 if (!*reply)
194 {
195 if (reply_type)
196 g_warning ("cgmanager method call org.linuxcontainers.cgmanager0_0.%s failed: %s. "
197 "Use G_DBUS_DEBUG=message for more info.", method_name, error->message);
198 g_error_free (error);
199 return false;
200 }
201 if (my_reply)
202 g_variant_unref (my_reply);
203 return true;
204 }
205
206 // todo - can we avoid some of this alloc/copy/free when copying
207 // from iters?
208
209 #define MAX_CONTROLLERS 20
210 bool cgm_get_controllers(char ***contrls)
211 {
212 char **list = NULL;
213 GVariantIter *iter = NULL;
214 GVariant *reply = NULL;
215 gchar *ctrl;
216 int i = 0;
217
218 if (!cgcall("ListControllers", NULL, G_VARIANT_TYPE("(as)"), &reply))
219 return false;
220
221 do {
222 list = malloc(MAX_CONTROLLERS * sizeof(*list));
223 } while (!list);
224 memset(list, 0, MAX_CONTROLLERS * sizeof(*list));
225 g_variant_get(reply, "(as)", &iter);
226 while (g_variant_iter_next(iter, "s", &ctrl)) {
227 if (i >= MAX_CONTROLLERS) {
228 g_warning("Too many cgroup subsystems");
229 exit(1);
230 }
231 do {
232 list[i] = strdup(ctrl);
233 } while (!list[i]);
234 i++;
235 g_free(ctrl);
236 }
237 g_variant_iter_free(iter);
238 g_variant_unref(reply);
239
240 *contrls = list;
241 return true;
242 }
243
244 void free_key(struct cgm_keys *k)
245 {
246 if (!k)
247 return;
248 free(k->name);
249 free(k);
250 }
251
252 void free_keys(struct cgm_keys **keys)
253 {
254 int i;
255
256 if (!keys)
257 return;
258 for (i = 0; keys[i]; i++) {
259 free_key(keys[i]);
260 }
261 free(keys);
262 }
263
264 #define BATCH_SIZE 50
265 void append_key(struct cgm_keys ***keys, struct cgm_keys *newk, size_t *sz, size_t *asz)
266 {
267 assert(keys);
268 if (sz == 0) {
269 *asz = BATCH_SIZE;
270 *sz = 1;
271 do {
272 *keys = malloc(*asz * sizeof(struct cgm_keys *));
273 } while (!*keys);
274 (*keys)[0] = newk;
275 (*keys)[1] = NULL;
276 return;
277 }
278 if (*sz + 2 >= *asz) {
279 struct cgm_keys **tmp;
280 *asz += BATCH_SIZE;
281 do {
282 tmp = realloc(*keys, *asz * sizeof(struct cgm_keys *));
283 } while (!tmp);
284 *keys = tmp;
285 }
286 (*keys)[(*sz)++] = newk;
287 (*keys)[(*sz)] = NULL;
288 }
289
290 bool cgm_list_keys(const char *controller, const char *cgroup, struct cgm_keys ***keys)
291 {
292 GVariantIter *iter = NULL;
293 GVariant *reply = NULL;
294 size_t sz = 0, asz = 0;
295 gchar *name;
296 guint32 uid, gid, mode;
297
298 if (!cgcall("ListKeys", g_variant_new("(ss)", controller, cgroup),
299 G_VARIANT_TYPE("(a(suuu))"), &reply))
300 return false;
301
302 g_variant_get(reply, "(a(suuu))", &iter);
303 while (g_variant_iter_next(iter, "(suuu)", &name, &uid, &gid, &mode)) {
304 /* name, owner, groupid, mode) */
305 struct cgm_keys *k;
306
307 do {
308 k = malloc(sizeof(*k));
309 } while (!k);
310 do {
311 k->name = strdup(name);
312 } while (!k->name);
313 k->uid = uid;
314 k->gid = gid;
315 k->mode = mode;
316 g_free(name);
317 append_key(keys, k, &sz, &asz);
318 }
319
320 g_variant_iter_free(iter);
321 g_variant_unref(reply);
322
323 return true;
324 }
325
326 bool cgm_list_children(const char *controller, const char *cgroup, char ***list)
327 {
328 GVariantIter *iter = NULL;
329 GVariant *reply = NULL;
330 gchar *child;
331 size_t sz = 0, asz = 0;
332
333 if (!cgcall("ListChildren", g_variant_new("(ss)", controller, cgroup),
334 G_VARIANT_TYPE("(as)"), &reply))
335 return false;
336
337 g_variant_get(reply, "(as)", &iter);
338 do {
339 *list = malloc(BATCH_SIZE * sizeof(char *));
340 } while (!*list);
341 (*list)[0] = NULL;
342 while (g_variant_iter_next(iter, "s", &child)) {
343 if (sz+2 >= asz) {
344 char **tmp;
345 asz += BATCH_SIZE;
346 do {
347 tmp = realloc(*list, asz * sizeof(char *));
348 } while (!tmp);
349 *list = tmp;
350 }
351 do {
352 (*list)[sz] = strdup(child);
353 } while (!(*list)[sz]);
354 (*list)[sz+1] = NULL;
355 sz++;
356 g_free(child);
357 }
358
359 g_variant_iter_free(iter);
360 g_variant_unref(reply);
361
362 return true;
363 }
364
365 bool cgm_escape_cgroup(void)
366 {
367 return cgcall("MovePidAbs", g_variant_new("(ssi)", "all", "/", getpid()),
368 G_VARIANT_TYPE_UNIT, NULL);
369 }
370
371 bool cgm_move_pid(const char *controller, const char *cgroup, pid_t pid)
372 {
373 return cgcall("MovePid", g_variant_new("(ssi)", controller, cgroup, pid),
374 G_VARIANT_TYPE_UNIT, NULL);
375 }
376
377 bool cgm_get_value(const char *controller, const char *cgroup, const char *file,
378 char **value)
379 {
380 GVariant *reply = NULL;
381 gchar *str;
382
383 if (!cgcall("GetValue", g_variant_new("(sss)", controller, cgroup, file),
384 G_VARIANT_TYPE("(s)"), &reply))
385 return false;
386
387 g_variant_get(reply, "(s)", &str);
388 g_variant_unref(reply);
389
390 do {
391 *value = strdup(str);
392 } while (!*value);
393 g_free(str);
394
395 return true;
396 }
397
398 bool cgm_set_value(const char *controller, const char *cgroup, const char *file,
399 const char *value)
400 {
401 return cgcall("SetValue", g_variant_new("(ssss)", controller, cgroup, file, value),
402 G_VARIANT_TYPE_UNIT, NULL);
403 }
404
405 bool cgm_create(const char *controller, const char *cg)
406 {
407 if (!cgcall("Create", g_variant_new("(ss)", controller, cg),
408 G_VARIANT_TYPE ("(i)"), NULL))
409 return false;
410
411 return true;
412 }
413
414 bool cgm_chown_file(const char *controller, const char *cg, uid_t uid, gid_t gid)
415 {
416 return cgcall("Chown", g_variant_new("(ssii)", controller, cg, uid, gid),
417 G_VARIANT_TYPE_UNIT, NULL);
418 }
419
420 bool cgm_chmod_file(const char *controller, const char *file, mode_t mode)
421 {
422 return cgcall("Chmod", g_variant_new("(sssi)", controller, file, "", mode), G_VARIANT_TYPE_UNIT, NULL);
423 }
424
425 bool cgm_remove(const char *controller, const char *cg)
426 {
427 return cgcall("Remove", g_variant_new ("(ssi)", "all", cg, 1), G_VARIANT_TYPE ("(i)"), NULL);
428 }