]>
Commit | Line | Data |
---|---|---|
2183082c | 1 | /* |
2c51f8dd | 2 | * Copyright © 2015 Canonical Limited |
2183082c SH |
3 | * |
4 | * Authors: | |
2c51f8dd | 5 | * Serge Hallyn <serge.hallyn@ubuntu.com> |
2183082c SH |
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> | |
ab54b798 | 38 | #include <sys/wait.h> |
2183082c SH |
39 | #include <netinet/in.h> |
40 | #include <net/if.h> | |
41 | #include <stdbool.h> | |
2c51f8dd SH |
42 | #include "cgmanager.h" |
43 | #include <assert.h> | |
2183082c | 44 | |
2c51f8dd SH |
45 | #include <glib.h> |
46 | #include <gio/gio.h> | |
2183082c | 47 | |
2c51f8dd SH |
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; | |
2183082c | 52 | |
2c51f8dd | 53 | static pthread_mutex_t cgm_mutex = PTHREAD_MUTEX_INITIALIZER; |
2183082c | 54 | |
2c51f8dd | 55 | static void lock_mutex(pthread_mutex_t *l) |
2183082c | 56 | { |
2c51f8dd SH |
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 | } | |
2183082c SH |
62 | } |
63 | ||
2c51f8dd | 64 | static void unlock_mutex(pthread_mutex_t *l) |
2183082c | 65 | { |
2c51f8dd SH |
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 | } | |
2183082c | 72 | |
2c51f8dd SH |
73 | void cgm_lock(void) |
74 | { | |
75 | lock_mutex(&cgm_mutex); | |
76 | } | |
2183082c | 77 | |
2c51f8dd SH |
78 | void cgm_unlock(void) |
79 | { | |
80 | unlock_mutex(&cgm_mutex); | |
81 | } | |
2183082c | 82 | |
2c51f8dd SH |
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); | |
2183082c | 95 | } |
2c51f8dd SH |
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; | |
2183082c SH |
103 | } |
104 | ||
2c51f8dd | 105 | bool cgm_dbus_connect(void) |
2183082c | 106 | { |
2c51f8dd SH |
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)) { | |
40b8c791 | 122 | // someone else reconnected us |
2c51f8dd SH |
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; | |
2183082c SH |
129 | } |
130 | ||
2c51f8dd SH |
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; | |
2183082c SH |
142 | } |
143 | ||
2c51f8dd SH |
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(); | |
2183082c SH |
170 | return true; |
171 | } | |
172 | ||
2c51f8dd SH |
173 | static bool cgcall(const gchar *method_name, GVariant *parameters, |
174 | const GVariantType *reply_type, GVariant **reply) | |
2183082c | 175 | { |
2c51f8dd SH |
176 | GVariant *my_reply = NULL; |
177 | GError *error = NULL; | |
178 | ||
2183082c | 179 | if (!cgm_dbus_connect()) { |
2c51f8dd | 180 | g_warning("Error: unable to connect to cgmanager"); |
2183082c SH |
181 | return false; |
182 | } | |
183 | ||
2c51f8dd SH |
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); | |
2183082c SH |
199 | return false; |
200 | } | |
2c51f8dd SH |
201 | if (my_reply) |
202 | g_variant_unref (my_reply); | |
2183082c SH |
203 | return true; |
204 | } | |
205 | ||
2c51f8dd SH |
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) | |
2183082c | 211 | { |
2c51f8dd SH |
212 | char **list = NULL; |
213 | GVariantIter *iter = NULL; | |
214 | GVariant *reply = NULL; | |
215 | gchar *ctrl; | |
216 | int i = 0; | |
2183082c | 217 | |
2c51f8dd | 218 | if (!cgcall("ListControllers", NULL, G_VARIANT_TYPE("(as)"), &reply)) |
2183082c | 219 | return false; |
2c51f8dd SH |
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); | |
2183082c | 236 | } |
2c51f8dd SH |
237 | g_variant_iter_free(iter); |
238 | g_variant_unref(reply); | |
2183082c | 239 | |
2c51f8dd | 240 | *contrls = list; |
2183082c SH |
241 | return true; |
242 | } | |
243 | ||
2c51f8dd | 244 | void free_key(struct cgm_keys *k) |
2183082c | 245 | { |
2c51f8dd SH |
246 | if (!k) |
247 | return; | |
248 | free(k->name); | |
249 | free(k); | |
2183082c SH |
250 | } |
251 | ||
2c51f8dd | 252 | void free_keys(struct cgm_keys **keys) |
2183082c | 253 | { |
2c51f8dd | 254 | int i; |
2183082c | 255 | |
2c51f8dd SH |
256 | if (!keys) |
257 | return; | |
258 | for (i = 0; keys[i]; i++) { | |
259 | free_key(keys[i]); | |
2183082c | 260 | } |
2c51f8dd | 261 | free(keys); |
2183082c | 262 | } |
99978832 | 263 | |
2c51f8dd SH |
264 | #define BATCH_SIZE 50 |
265 | void append_key(struct cgm_keys ***keys, struct cgm_keys *newk, size_t *sz, size_t *asz) | |
4775fba1 | 266 | { |
2c51f8dd SH |
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; | |
4775fba1 | 277 | } |
2c51f8dd SH |
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; | |
4775fba1 | 285 | } |
2c51f8dd SH |
286 | (*keys)[(*sz)++] = newk; |
287 | (*keys)[(*sz)] = NULL; | |
4775fba1 SH |
288 | } |
289 | ||
2c51f8dd | 290 | bool cgm_list_keys(const char *controller, const char *cgroup, struct cgm_keys ***keys) |
99978832 | 291 | { |
2c51f8dd SH |
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)) | |
99978832 | 300 | return false; |
99978832 | 301 | |
2c51f8dd SH |
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); | |
99978832 SH |
318 | } |
319 | ||
2c51f8dd SH |
320 | g_variant_iter_free(iter); |
321 | g_variant_unref(reply); | |
322 | ||
99978832 SH |
323 | return true; |
324 | } | |
ab54b798 | 325 | |
2c51f8dd | 326 | bool cgm_list_children(const char *controller, const char *cgroup, char ***list) |
2ad6d2bd | 327 | { |
2c51f8dd SH |
328 | GVariantIter *iter = NULL; |
329 | GVariant *reply = NULL; | |
330 | gchar *child; | |
331 | size_t sz = 0, asz = 0; | |
2ad6d2bd | 332 | |
2c51f8dd SH |
333 | if (!cgcall("ListChildren", g_variant_new("(ss)", controller, cgroup), |
334 | G_VARIANT_TYPE("(as)"), &reply)) | |
2ad6d2bd | 335 | return false; |
2c51f8dd SH |
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); | |
2ad6d2bd SH |
357 | } |
358 | ||
2c51f8dd SH |
359 | g_variant_iter_free(iter); |
360 | g_variant_unref(reply); | |
361 | ||
2ad6d2bd SH |
362 | return true; |
363 | } | |
364 | ||
2c51f8dd | 365 | bool cgm_escape_cgroup(void) |
ab54b798 | 366 | { |
2c51f8dd SH |
367 | return cgcall("MovePidAbs", g_variant_new("(ssi)", "all", "/", getpid()), |
368 | G_VARIANT_TYPE_UNIT, NULL); | |
ab54b798 SH |
369 | } |
370 | ||
2c51f8dd | 371 | bool cgm_move_pid(const char *controller, const char *cgroup, pid_t pid) |
ab54b798 | 372 | { |
2c51f8dd SH |
373 | return cgcall("MovePid", g_variant_new("(ssi)", controller, cgroup, pid), |
374 | G_VARIANT_TYPE_UNIT, NULL); | |
375 | } | |
ab54b798 | 376 | |
2c51f8dd SH |
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; | |
ab54b798 | 382 | |
2c51f8dd SH |
383 | if (!cgcall("GetValue", g_variant_new("(sss)", controller, cgroup, file), |
384 | G_VARIANT_TYPE("(s)"), &reply)) | |
385 | return false; | |
ab54b798 | 386 | |
2c51f8dd SH |
387 | g_variant_get(reply, "(s)", &str); |
388 | g_variant_unref(reply); | |
ab54b798 | 389 | |
2c51f8dd SH |
390 | do { |
391 | *value = strdup(str); | |
392 | } while (!*value); | |
393 | g_free(str); | |
ab54b798 | 394 | |
2c51f8dd | 395 | return true; |
ab54b798 SH |
396 | } |
397 | ||
2c51f8dd SH |
398 | bool cgm_set_value(const char *controller, const char *cgroup, const char *file, |
399 | const char *value) | |
ab54b798 | 400 | { |
2c51f8dd SH |
401 | return cgcall("SetValue", g_variant_new("(ssss)", controller, cgroup, file, value), |
402 | G_VARIANT_TYPE_UNIT, NULL); | |
403 | } | |
ab54b798 | 404 | |
2c51f8dd SH |
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)) | |
ab54b798 | 409 | return false; |
ab54b798 | 410 | |
ab54b798 SH |
411 | return true; |
412 | } | |
413 | ||
2c51f8dd | 414 | bool cgm_chown_file(const char *controller, const char *cg, uid_t uid, gid_t gid) |
0a1bb5ea | 415 | { |
2c51f8dd SH |
416 | return cgcall("Chown", g_variant_new("(ssii)", controller, cg, uid, gid), |
417 | G_VARIANT_TYPE_UNIT, NULL); | |
418 | } | |
0a1bb5ea | 419 | |
2c51f8dd SH |
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); | |
0a1bb5ea SH |
423 | } |
424 | ||
ab54b798 SH |
425 | bool cgm_remove(const char *controller, const char *cg) |
426 | { | |
2c51f8dd | 427 | return cgcall("Remove", g_variant_new ("(ssi)", "all", cg, 1), G_VARIANT_TYPE ("(i)"), NULL); |
ab54b798 | 428 | } |