]>
Commit | Line | Data |
---|---|---|
576f946d | 1 | /* |
2 | * lxc: linux Container library | |
3 | * | |
4 | * (C) Copyright IBM Corp. 2007, 2008 | |
5 | * | |
6 | * Authors: | |
9afe19d6 | 7 | * Daniel Lezcano <daniel.lezcano at free.fr> |
576f946d | 8 | * |
9 | * This library is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU Lesser General Public | |
11 | * License as published by the Free Software Foundation; either | |
12 | * version 2.1 of the License, or (at your option) any later version. | |
13 | * | |
14 | * This library is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * Lesser General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU Lesser General Public | |
20 | * License along with this library; if not, write to the Free Software | |
250b1eec | 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
576f946d | 22 | */ |
23 | #define _GNU_SOURCE | |
24 | #include <stdio.h> | |
25 | #undef _GNU_SOURCE | |
26 | #include <stdlib.h> | |
27 | #include <errno.h> | |
576f946d | 28 | #include <unistd.h> |
29 | #include <string.h> | |
341a9bd8 | 30 | #include <dirent.h> |
576f946d | 31 | #include <fcntl.h> |
b98f7d6e | 32 | #include <ctype.h> |
576f946d | 33 | #include <sys/types.h> |
34 | #include <sys/stat.h> | |
35 | #include <sys/param.h> | |
36 | #include <sys/inotify.h> | |
37 | #include <netinet/in.h> | |
38 | #include <net/if.h> | |
39 | ||
e2bcd7db | 40 | #include "error.h" |
881450bb | 41 | #include "config.h" |
ae5c8b8e | 42 | #include "commands.h" |
b98f7d6e SH |
43 | #include "list.h" |
44 | #include "conf.h" | |
36eb9bde | 45 | |
36eb9bde | 46 | #include <lxc/log.h> |
00b3c2e2 CLG |
47 | #include <lxc/cgroup.h> |
48 | #include <lxc/start.h> | |
36eb9bde | 49 | |
edaf8b1b SG |
50 | #if IS_BIONIC |
51 | #include <../include/lxcmntent.h> | |
52 | #else | |
53 | #include <mntent.h> | |
54 | #endif | |
55 | ||
120ce443 SG |
56 | #ifndef HAVE_GETLINE |
57 | #ifdef HAVE_FGETLN | |
58 | #include <../include/getline.h> | |
59 | #endif | |
60 | #endif | |
61 | ||
36eb9bde | 62 | lxc_log_define(lxc_cgroup, lxc); |
576f946d | 63 | |
5193cc3d | 64 | #define MTAB "/proc/mounts" |
576f946d | 65 | |
fd37327f ÇO |
66 | /* In the case of a bind mount, there could be two long pathnames in the |
67 | * mntent plus options so use large enough buffer size | |
68 | */ | |
69 | #define LARGE_MAXPATHLEN 4 * MAXPATHLEN | |
70 | ||
1d39a065 DW |
71 | /* Check if a mount is a cgroup hierarchy for any subsystem. |
72 | * Return the first subsystem found (or NULL if none). | |
73 | */ | |
74 | static char *mount_has_subsystem(const struct mntent *mntent) | |
75 | { | |
76 | FILE *f; | |
5270bf4b | 77 | char *c, *ret = NULL; |
1d39a065 DW |
78 | char line[MAXPATHLEN]; |
79 | ||
80 | /* read the list of subsystems from the kernel */ | |
81 | f = fopen("/proc/cgroups", "r"); | |
82 | if (!f) | |
83 | return 0; | |
84 | ||
85 | /* skip the first line, which contains column headings */ | |
00b6be44 SH |
86 | if (!fgets(line, MAXPATHLEN, f)) { |
87 | fclose(f); | |
1d39a065 | 88 | return 0; |
00b6be44 | 89 | } |
1d39a065 DW |
90 | |
91 | while (fgets(line, MAXPATHLEN, f)) { | |
92 | c = strchr(line, '\t'); | |
93 | if (!c) | |
94 | continue; | |
95 | *c = '\0'; | |
96 | ||
97 | ret = hasmntopt(mntent, line); | |
98 | if (ret) | |
99 | break; | |
100 | } | |
101 | ||
102 | fclose(f); | |
103 | return ret; | |
104 | } | |
105 | ||
d08ba6ec | 106 | /* |
23622a2a | 107 | * Determine mountpoint for a cgroup subsystem. |
b98f7d6e | 108 | * @dest: a passed-in buffer of at least size MAXPATHLEN into which the path |
ae5c8b8e | 109 | * is copied. |
b98f7d6e | 110 | * @subsystem: cgroup subsystem (i.e. freezer) |
ae5c8b8e | 111 | * |
b98f7d6e | 112 | * Returns true on success, false on error. |
ae5c8b8e | 113 | */ |
b98f7d6e | 114 | bool get_subsys_mount(char *dest, const char *subsystem) |
576f946d | 115 | { |
93d564ed | 116 | struct mntent mntent_r; |
bcbd102c | 117 | FILE *file = NULL; |
b98f7d6e SH |
118 | int ret; |
119 | bool retv = false; | |
fd37327f ÇO |
120 | char buf[LARGE_MAXPATHLEN] = {0}; |
121 | ||
bcbd102c SH |
122 | file = setmntent(MTAB, "r"); |
123 | if (!file) { | |
124 | SYSERROR("failed to open %s", MTAB); | |
5193cc3d | 125 | return -1; |
bcbd102c | 126 | } |
0d9f8e18 | 127 | |
93d564ed | 128 | while ((getmntent_r(file, &mntent_r, buf, sizeof(buf)))) { |
b98f7d6e | 129 | if (strcmp(mntent_r.mnt_type, "cgroup")) |
bcbd102c | 130 | continue; |
ef6e34ee | 131 | |
1d39a065 | 132 | if (subsystem) { |
fd37327f | 133 | if (!hasmntopt(&mntent_r, subsystem)) |
1d39a065 | 134 | continue; |
ae5c8b8e | 135 | } else { |
fd37327f | 136 | if (!mount_has_subsystem(&mntent_r)) |
1d39a065 DW |
137 | continue; |
138 | } | |
ad08bbb7 | 139 | |
b98f7d6e | 140 | ret = snprintf(dest, MAXPATHLEN, "%s", mntent_r.mnt_dir); |
ad08bbb7 DW |
141 | if (ret < 0 || ret >= MAXPATHLEN) |
142 | goto fail; | |
143 | ||
b98f7d6e | 144 | retv = true; |
ad08bbb7 | 145 | goto out; |
bcbd102c | 146 | }; |
576f946d | 147 | |
d08ba6ec SH |
148 | fail: |
149 | DEBUG("Failed to find cgroup for %s\n", | |
150 | subsystem ? subsystem : "(NULL)"); | |
ad08bbb7 DW |
151 | out: |
152 | endmntent(file); | |
b98f7d6e | 153 | return retv; |
5193cc3d DL |
154 | } |
155 | ||
ae5c8b8e | 156 | /* |
b98f7d6e SH |
157 | * is_in_cgroup: check whether pid is found in the passed-in cgroup tasks |
158 | * file. | |
159 | * @path: in full path to a cgroup tasks file | |
160 | * Note that in most cases the file will simply not exist, which is ok - it | |
161 | * just means that's not our cgroup. | |
ae5c8b8e | 162 | */ |
b98f7d6e | 163 | static bool is_in_cgroup(pid_t pid, char *path) |
0b9c21ab | 164 | { |
b98f7d6e SH |
165 | int cmppid; |
166 | FILE *f = fopen(path, "r"); | |
167 | char *line = NULL; | |
168 | size_t sz = 0; | |
fd37327f | 169 | |
b98f7d6e SH |
170 | if (!f) |
171 | return false; | |
172 | while (getline(&line, &sz, f) != -1) { | |
173 | if (sscanf(line, "%d", &cmppid) == 1 && cmppid == pid) { | |
174 | fclose(f); | |
175 | free(line); | |
176 | return true; | |
ae5c8b8e | 177 | } |
ae5c8b8e | 178 | } |
b98f7d6e SH |
179 | fclose(f); |
180 | if (line) | |
181 | free(line); | |
182 | return false; | |
0b9c21ab SH |
183 | } |
184 | ||
ae5c8b8e | 185 | /* |
2acf7795 DE |
186 | * lxc_cgroup_path_get: Get the absolute pathname for a cgroup |
187 | * file for a running container. | |
188 | * | |
189 | * @subsystem : subsystem of interest (e.g. "freezer"). If NULL, then | |
190 | * the first cgroup entry in mtab will be used. | |
191 | * @name : name of container to connect to | |
192 | * @lxcpath : the lxcpath in which the container is running | |
ae5c8b8e SH |
193 | * |
194 | * This is the exported function, which determines cgpath from the | |
2acf7795 | 195 | * lxc-start of the @name container running in @lxcpath. |
ae5c8b8e | 196 | * |
2acf7795 DE |
197 | * Returns path on success, NULL on error. The caller must free() |
198 | * the returned path. | |
ae5c8b8e | 199 | */ |
2acf7795 DE |
200 | char *lxc_cgroup_path_get(const char *subsystem, const char *name, |
201 | const char *lxcpath) | |
fd4f5a56 | 202 | { |
b98f7d6e SH |
203 | char *cgpath, *cgp, path[MAXPATHLEN], *pathp, *p; |
204 | pid_t initpid = lxc_cmd_get_init_pid(name, lxcpath); | |
205 | int ret; | |
206 | ||
6fe93aa1 | 207 | if (initpid < 0) |
b98f7d6e | 208 | return NULL; |
fd4f5a56 | 209 | |
b98f7d6e SH |
210 | cgpath = lxc_cmd_get_cgroup_path(name, lxcpath, subsystem); |
211 | if (!cgpath) | |
2acf7795 | 212 | return NULL; |
fd4f5a56 | 213 | |
b98f7d6e SH |
214 | if (!get_subsys_mount(path, subsystem)) |
215 | return NULL; | |
216 | ||
217 | pathp = path + strlen(path); | |
218 | /* | |
219 | * find a mntpt where i have the subsystem mounted, then find | |
220 | * a subset cgpath under that which has pid in it. | |
221 | * | |
222 | * If d->mntpt is '/a/b/c/d', and the mountpoint is /x/y/z, | |
223 | * then look for ourselves in: | |
224 | * /x/y/z/a/b/c/d/tasks | |
225 | * /x/y/z/b/c/d/tasks | |
226 | * /x/y/z/c/d/tasks | |
227 | * /x/y/z/d/tasks | |
228 | * /x/y/z/tasks | |
229 | */ | |
230 | cgp = cgpath; | |
231 | while (cgp[0]) { | |
232 | ret = snprintf(pathp, MAXPATHLEN - (pathp - path), "%s/tasks", cgp); | |
233 | if (ret < 0 || ret >= MAXPATHLEN) | |
234 | return NULL; | |
235 | if (!is_in_cgroup(initpid, path)) { | |
236 | // does not exist, try the next one | |
237 | cgp = index(cgp+1, '/'); | |
238 | if (!cgp) | |
239 | break; | |
240 | continue; | |
241 | } | |
242 | break; | |
243 | } | |
244 | if (!cgp || !*cgp) { | |
245 | // try just the path | |
246 | ret = snprintf(pathp, MAXPATHLEN - (pathp - path), "/tasks"); | |
247 | if (ret < 0 || ret >= MAXPATHLEN) | |
248 | return NULL; | |
249 | if (!is_in_cgroup(initpid, path)) { | |
250 | return NULL; | |
251 | } | |
252 | return strdup("/"); | |
253 | } | |
254 | // path still has 'tasks' on the end, drop it | |
c32981c3 | 255 | if ((p = strrchr(path, '/')) != NULL) |
b98f7d6e SH |
256 | *p = '\0'; |
257 | return strdup(path); | |
ae5c8b8e SH |
258 | } |
259 | ||
260 | /* | |
2acf7795 DE |
261 | * do_cgroup_set: Write a value into a cgroup file |
262 | * | |
263 | * @path : absolute path to cgroup file | |
264 | * @value : value to write into file | |
265 | * | |
266 | * Returns 0 on success, < 0 on error. | |
ae5c8b8e SH |
267 | */ |
268 | static int do_cgroup_set(const char *path, const char *value) | |
269 | { | |
270 | int fd, ret; | |
271 | ||
272 | if ((fd = open(path, O_WRONLY)) < 0) { | |
273 | SYSERROR("open %s : %s", path, strerror(errno)); | |
274 | return -1; | |
fd4f5a56 DL |
275 | } |
276 | ||
ae5c8b8e SH |
277 | if ((ret = write(fd, value, strlen(value))) < 0) { |
278 | close(fd); | |
279 | SYSERROR("write %s : %s", path, strerror(errno)); | |
280 | return ret; | |
281 | } | |
fd4f5a56 | 282 | |
ae5c8b8e SH |
283 | if ((ret = close(fd)) < 0) { |
284 | SYSERROR("close %s : %s", path, strerror(errno)); | |
285 | return ret; | |
286 | } | |
287 | return 0; | |
fd4f5a56 DL |
288 | } |
289 | ||
e14f67a7 U |
290 | static int in_subsys_list(const char *s, const char *list) |
291 | { | |
292 | char *token, *str, *saveptr = NULL; | |
293 | ||
294 | if (!list || !s) | |
295 | return 0; | |
296 | ||
297 | str = alloca(strlen(list)+1); | |
298 | strcpy(str, list); | |
299 | for (; (token = strtok_r(str, ",", &saveptr)); str = NULL) { | |
300 | if (strcmp(s, token) == 0) | |
301 | return 1; | |
302 | } | |
303 | ||
304 | return 0; | |
305 | } | |
306 | ||
307 | static char *cgroup_get_subsys_abspath(struct lxc_handler *handler, const char *subsys) | |
308 | { | |
309 | struct cgroup_desc *d; | |
310 | ||
311 | for (d = handler->cgroup; d; d = d->next) { | |
312 | if (in_subsys_list(subsys, d->subsystems)) | |
313 | return d->curcgroup; | |
314 | } | |
315 | ||
316 | return NULL; | |
317 | } | |
318 | ||
b98f7d6e SH |
319 | static bool cgroup_devices_has_deny(struct lxc_handler *h, char *v) |
320 | { | |
321 | char *cgabspath, path[MAXPATHLEN]; | |
322 | FILE *f; | |
323 | char *line = NULL; | |
324 | size_t len = 0; | |
325 | bool ret = true; | |
e14f67a7 | 326 | int r; |
b98f7d6e SH |
327 | |
328 | // XXX FIXME if users could use something other than 'lxc.devices.deny = a'. | |
329 | // not sure they ever do, but they *could* | |
330 | // right now, I'm assuming they do NOT | |
331 | if (strcmp(v, "a") && strcmp(v, "a *:* rwm")) | |
332 | return false; | |
e14f67a7 | 333 | cgabspath = cgroup_get_subsys_abspath(h, "devices"); |
b98f7d6e | 334 | if (!cgabspath) |
e14f67a7 | 335 | return false; |
b98f7d6e | 336 | |
e14f67a7 U |
337 | r = snprintf(path, MAXPATHLEN, "%s/devices.list", cgabspath); |
338 | if (r < 0 || r >= MAXPATHLEN) { | |
b98f7d6e | 339 | ERROR("pathname too long for devices.list"); |
e14f67a7 | 340 | return false; |
b98f7d6e SH |
341 | } |
342 | ||
6fe93aa1 | 343 | if (!(f = fopen(path, "r"))) |
e14f67a7 | 344 | return false; |
b98f7d6e SH |
345 | |
346 | while (getline(&line, &len, f) != -1) { | |
347 | size_t len = strlen(line); | |
348 | if (len > 0 && line[len-1] == '\n') | |
349 | line[len-1] = '\0'; | |
350 | if (strcmp(line, "a *:* rwm") == 0) { | |
351 | ret = false; | |
352 | goto out; | |
353 | } | |
354 | } | |
355 | ||
356 | out: | |
357 | fclose(f); | |
358 | if (line) | |
359 | free(line); | |
360 | return ret; | |
361 | } | |
362 | ||
363 | static bool cgroup_devices_has_allow(struct lxc_handler *h, char *v) | |
364 | { | |
365 | char *cgabspath, path[MAXPATHLEN]; | |
e14f67a7 | 366 | int r; |
b98f7d6e SH |
367 | bool ret = false; |
368 | FILE *f; | |
369 | char *line = NULL; | |
370 | size_t len = 0; | |
371 | ||
e14f67a7 | 372 | cgabspath = cgroup_get_subsys_abspath(h, "devices"); |
b98f7d6e | 373 | if (!cgabspath) |
e14f67a7 | 374 | return false; |
b98f7d6e | 375 | |
e14f67a7 U |
376 | r = snprintf(path, MAXPATHLEN, "%s/devices.list", cgabspath); |
377 | if (r < 0 || r >= MAXPATHLEN) { | |
b98f7d6e | 378 | ERROR("pathname too long to for devices.list"); |
e14f67a7 | 379 | return false; |
b98f7d6e SH |
380 | } |
381 | ||
6fe93aa1 | 382 | if (!(f = fopen(path, "r"))) |
e14f67a7 | 383 | return false; |
b98f7d6e SH |
384 | |
385 | while (getline(&line, &len, f) != -1) { | |
e14f67a7 U |
386 | if (len < 1) |
387 | goto out; | |
388 | if (line[len-1] == '\n') | |
b98f7d6e | 389 | line[len-1] = '\0'; |
e14f67a7 | 390 | if (strcmp(line, "a *:* rwm") == 0 || strcmp(line, v) == 0) { |
b98f7d6e SH |
391 | ret = true; |
392 | goto out; | |
393 | } | |
394 | } | |
395 | ||
396 | out: | |
397 | if (line) | |
398 | free(line); | |
399 | fclose(f); | |
400 | return ret; | |
401 | } | |
402 | ||
ae5c8b8e | 403 | /* |
2acf7795 DE |
404 | * lxc_cgroup_set_bypath: Write a value into a cgroup file |
405 | * | |
406 | * @cgrelpath : a container's relative cgroup path (e.g. "lxc/c1") | |
407 | * @filename : the cgroup file to write (e.g. "freezer.state") | |
408 | * @value : value to write into file | |
ae5c8b8e SH |
409 | * |
410 | * Returns 0 on success, < 0 on error. | |
411 | */ | |
b98f7d6e SH |
412 | int lxc_cgroup_set_value(struct lxc_handler *handler, const char *filename, |
413 | const char *value) | |
fd4f5a56 | 414 | { |
b98f7d6e | 415 | char *cgabspath, path[MAXPATHLEN], *p; |
ae5c8b8e | 416 | int ret; |
c8f7c563 | 417 | |
b98f7d6e SH |
418 | ret = snprintf(path, MAXPATHLEN, "%s", filename); |
419 | if (ret < 0 || ret >= MAXPATHLEN) | |
420 | return -1; | |
421 | if ((p = index(path, '.')) != NULL) | |
422 | *p = '\0'; | |
e14f67a7 | 423 | cgabspath = cgroup_get_subsys_abspath(handler, path); |
2acf7795 DE |
424 | if (!cgabspath) |
425 | return -1; | |
c8f7c563 | 426 | |
2acf7795 | 427 | ret = snprintf(path, MAXPATHLEN, "%s/%s", cgabspath, filename); |
ae5c8b8e | 428 | if (ret < 0 || ret >= MAXPATHLEN) { |
b98f7d6e SH |
429 | ERROR("pathname too long to set cgroup value %s to %s", |
430 | filename, value); | |
431 | return -1; | |
ae5c8b8e | 432 | } |
c8f7c563 | 433 | |
b98f7d6e | 434 | return do_cgroup_set(path, value); |
c8f7c563 CS |
435 | } |
436 | ||
ae5c8b8e | 437 | /* |
2acf7795 | 438 | * lxc_cgroup_set: Write a value into a cgroup file |
ae5c8b8e | 439 | * |
2acf7795 DE |
440 | * @name : name of container to connect to |
441 | * @filename : the cgroup file to write (e.g. "freezer.state") | |
442 | * @value : value to write into file | |
443 | * @lxcpath : the lxcpath in which the container is running | |
ae5c8b8e SH |
444 | * |
445 | * Returns 0 on success, < 0 on error. | |
446 | */ | |
ae5c8b8e SH |
447 | int lxc_cgroup_set(const char *name, const char *filename, const char *value, |
448 | const char *lxcpath) | |
c8f7c563 | 449 | { |
ae5c8b8e | 450 | int ret; |
2acf7795 | 451 | char *cgabspath; |
ae5c8b8e | 452 | char path[MAXPATHLEN]; |
b98f7d6e SH |
453 | char *subsystem = alloca(strlen(filename)+1), *p; |
454 | strcpy(subsystem, filename); | |
455 | ||
456 | if ((p = index(subsystem, '.')) != NULL) | |
457 | *p = '\0'; | |
ae5c8b8e | 458 | |
b98f7d6e | 459 | cgabspath = lxc_cgroup_path_get(subsystem, name, lxcpath); |
2acf7795 DE |
460 | if (!cgabspath) |
461 | return -1; | |
ae5c8b8e | 462 | |
2acf7795 | 463 | ret = snprintf(path, MAXPATHLEN, "%s/%s", cgabspath, filename); |
ae5c8b8e SH |
464 | if (ret < 0 || ret >= MAXPATHLEN) { |
465 | ERROR("pathname too long"); | |
2acf7795 DE |
466 | ret = -1; |
467 | goto out; | |
ae5c8b8e SH |
468 | } |
469 | ||
2acf7795 | 470 | ret = do_cgroup_set(path, value); |
fd37327f | 471 | |
2acf7795 DE |
472 | out: |
473 | free(cgabspath); | |
474 | return ret; | |
c8f7c563 CS |
475 | } |
476 | ||
ae5c8b8e | 477 | /* |
2acf7795 | 478 | * lxc_cgroup_get: Read value from a cgroup file |
ae5c8b8e | 479 | * |
2acf7795 DE |
480 | * @name : name of container to connect to |
481 | * @filename : the cgroup file to read (e.g. "freezer.state") | |
482 | * @value : a pre-allocated buffer to copy the answer into | |
483 | * @len : the length of pre-allocated @value | |
484 | * @lxcpath : the lxcpath in which the container is running | |
ae5c8b8e | 485 | * |
2acf7795 | 486 | * Returns the number of bytes read on success, < 0 on error |
ae5c8b8e | 487 | * |
2acf7795 DE |
488 | * If you pass in NULL value or 0 len, the return value will be the size of |
489 | * the file, and @value will not contain the contents. | |
ae5c8b8e SH |
490 | * |
491 | * Note that we can't get the file size quickly through stat or lseek. | |
492 | * Therefore if you pass in len > 0 but less than the file size, your only | |
493 | * indication will be that the return value will be equal to the passed-in ret. | |
494 | * We will not return the actual full file size. | |
495 | */ | |
496 | int lxc_cgroup_get(const char *name, const char *filename, char *value, | |
497 | size_t len, const char *lxcpath) | |
c8f7c563 | 498 | { |
2acf7795 DE |
499 | int fd, ret; |
500 | char *cgabspath; | |
ae5c8b8e | 501 | char path[MAXPATHLEN]; |
b98f7d6e | 502 | char *subsystem = alloca(strlen(filename)+1), *p; |
460a1cf0 | 503 | |
b98f7d6e SH |
504 | strcpy(subsystem, filename); |
505 | ||
506 | if ((p = index(subsystem, '.')) != NULL) | |
507 | *p = '\0'; | |
508 | ||
509 | cgabspath = lxc_cgroup_path_get(subsystem, name, lxcpath); | |
2acf7795 DE |
510 | if (!cgabspath) |
511 | return -1; | |
fd4f5a56 | 512 | |
2acf7795 DE |
513 | ret = snprintf(path, MAXPATHLEN, "%s/%s", cgabspath, filename); |
514 | if (ret < 0 || ret >= MAXPATHLEN) { | |
9ba8130c | 515 | ERROR("pathname too long"); |
2acf7795 DE |
516 | ret = -1; |
517 | goto out; | |
9ba8130c | 518 | } |
fd4f5a56 | 519 | |
ae5c8b8e | 520 | fd = open(path, O_RDONLY); |
c8f7c563 | 521 | if (fd < 0) { |
ae5c8b8e | 522 | ERROR("open %s : %s", path, strerror(errno)); |
2acf7795 DE |
523 | ret = -1; |
524 | goto out; | |
fd4f5a56 DL |
525 | } |
526 | ||
ae5c8b8e SH |
527 | if (!len || !value) { |
528 | char buf[100]; | |
529 | int count = 0; | |
530 | while ((ret = read(fd, buf, 100)) > 0) | |
531 | count += ret; | |
532 | if (ret >= 0) | |
533 | ret = count; | |
534 | } else { | |
535 | memset(value, 0, len); | |
536 | ret = read(fd, value, len); | |
fd4f5a56 DL |
537 | } |
538 | ||
ae5c8b8e SH |
539 | if (ret < 0) |
540 | ERROR("read %s : %s", path, strerror(errno)); | |
541 | ||
542 | close(fd); | |
2acf7795 DE |
543 | out: |
544 | free(cgabspath); | |
ae5c8b8e | 545 | return ret; |
c8f7c563 CS |
546 | } |
547 | ||
b98f7d6e | 548 | int lxc_cgroup_nrtasks(struct lxc_handler *handler) |
c8f7c563 | 549 | { |
ae5c8b8e | 550 | char path[MAXPATHLEN]; |
2acf7795 | 551 | int pid, ret; |
ae5c8b8e | 552 | FILE *file; |
c8f7c563 | 553 | |
b98f7d6e | 554 | if (!handler->cgroup) |
2acf7795 | 555 | return -1; |
c8f7c563 | 556 | |
b98f7d6e SH |
557 | /* XXX Should we use a specific subsystem rather than the first one we |
558 | * found (handler->cgroup->curcgroup)? */ | |
559 | ret = snprintf(path, MAXPATHLEN, "%s/tasks", handler->cgroup->curcgroup); | |
2acf7795 | 560 | if (ret < 0 || ret >= MAXPATHLEN) { |
ae5c8b8e | 561 | ERROR("pathname too long"); |
b98f7d6e | 562 | return -1; |
ae5c8b8e | 563 | } |
c8f7c563 | 564 | |
ae5c8b8e SH |
565 | file = fopen(path, "r"); |
566 | if (!file) { | |
567 | SYSERROR("fopen '%s' failed", path); | |
b98f7d6e | 568 | return -1; |
c8f7c563 CS |
569 | } |
570 | ||
2acf7795 | 571 | ret = 0; |
ae5c8b8e | 572 | while (fscanf(file, "%d", &pid) != EOF) |
2acf7795 | 573 | ret++; |
fd4f5a56 | 574 | |
ae5c8b8e | 575 | fclose(file); |
2acf7795 | 576 | return ret; |
fd4f5a56 DL |
577 | } |
578 | ||
4f17323e CS |
579 | static int subsys_lists_match(const char *list1, const char *list2) |
580 | { | |
581 | char *token, *str, *saveptr = NULL; | |
582 | ||
583 | if (!list1 || !list2) | |
584 | return 0; | |
585 | ||
586 | if (strlen(list1) != strlen(list2)) | |
587 | return 0; | |
588 | ||
589 | str = alloca(strlen(list1)+1); | |
590 | strcpy(str, list1); | |
591 | for (; (token = strtok_r(str, ",", &saveptr)); str = NULL) { | |
592 | if (in_subsys_list(token, list2) == 0) | |
593 | return 0; | |
594 | } | |
595 | ||
596 | return 1; | |
597 | } | |
598 | ||
b98f7d6e | 599 | static void set_clone_children(struct mntent *m) |
fc7de561 SH |
600 | { |
601 | char path[MAXPATHLEN]; | |
602 | FILE *fout; | |
603 | int ret; | |
604 | ||
b98f7d6e SH |
605 | if (!in_subsys_list("cpuset", m->mnt_opts)) |
606 | return; | |
607 | ret = snprintf(path, MAXPATHLEN, "%s/cgroup.clone_children", m->mnt_dir); | |
fc7de561 SH |
608 | if (ret < 0 || ret > MAXPATHLEN) |
609 | return; | |
610 | fout = fopen(path, "w"); | |
611 | if (!fout) | |
612 | return; | |
613 | fprintf(fout, "1\n"); | |
614 | fclose(fout); | |
615 | } | |
616 | ||
b98f7d6e SH |
617 | static bool have_visited(char *opts, char *visited, char *all_subsystems) |
618 | { | |
619 | char *str, *s = NULL, *token; | |
620 | ||
621 | str = alloca(strlen(opts)+1); | |
622 | strcpy(str, opts); | |
623 | for (; (token = strtok_r(str, ",", &s)); str = NULL) { | |
624 | if (!in_subsys_list(token, all_subsystems)) | |
625 | continue; | |
626 | if (visited && in_subsys_list(token, visited)) | |
627 | return true; | |
628 | } | |
629 | ||
630 | return false; | |
631 | } | |
632 | ||
633 | static bool is_in_desclist(struct cgroup_desc *d, char *opts, char *all_subsystems) | |
634 | { | |
635 | while (d) { | |
636 | if (have_visited(opts, d->subsystems, all_subsystems)) | |
637 | return true; | |
638 | d = d->next; | |
639 | } | |
640 | return false; | |
641 | } | |
642 | ||
643 | static char *record_visited(char *opts, char *all_subsystems) | |
644 | { | |
645 | char *s = NULL, *token, *str; | |
646 | int oldlen = 0, newlen, toklen; | |
647 | char *visited = NULL; | |
648 | ||
649 | str = alloca(strlen(opts)+1); | |
650 | strcpy(str, opts); | |
651 | for (; (token = strtok_r(str, ",", &s)); str = NULL) { | |
652 | if (!in_subsys_list(token, all_subsystems)) | |
653 | continue; | |
654 | toklen = strlen(token); | |
655 | newlen = oldlen + toklen + 1; // ',' + token or token + '\0' | |
656 | visited = realloc(visited, newlen); | |
657 | if (!visited) | |
658 | return (char *)-ENOMEM; | |
659 | if (oldlen) | |
660 | strcat(visited, ","); | |
661 | else | |
662 | *visited = '\0'; | |
663 | strcat(visited, token); | |
e14f67a7 | 664 | oldlen = newlen; |
b98f7d6e SH |
665 | } |
666 | ||
667 | return visited; | |
668 | } | |
55c76589 | 669 | |
b98f7d6e | 670 | static char *get_all_subsystems(void) |
9a93d992 SH |
671 | { |
672 | FILE *f; | |
673 | char *line = NULL, *ret = NULL; | |
674 | size_t len; | |
675 | int first = 1; | |
676 | ||
677 | /* read the list of subsystems from the kernel */ | |
678 | f = fopen("/proc/cgroups", "r"); | |
679 | if (!f) | |
680 | return NULL; | |
681 | ||
682 | while (getline(&line, &len, f) != -1) { | |
683 | char *c; | |
684 | int oldlen, newlen, inc; | |
685 | ||
686 | /* skip the first line */ | |
687 | if (first) { | |
688 | first=0; | |
689 | continue; | |
690 | } | |
691 | ||
692 | c = strchr(line, '\t'); | |
693 | if (!c) | |
694 | continue; | |
695 | *c = '\0'; | |
696 | ||
697 | oldlen = ret ? strlen(ret) : 0; | |
698 | newlen = oldlen + strlen(line) + 2; | |
699 | ret = realloc(ret, newlen); | |
700 | if (!ret) | |
701 | goto out; | |
702 | inc = snprintf(ret + oldlen, newlen, ",%s", line); | |
703 | if (inc < 0 || inc >= newlen) { | |
704 | free(ret); | |
705 | ret = NULL; | |
706 | goto out; | |
707 | } | |
708 | } | |
709 | ||
710 | out: | |
fa9ac567 SH |
711 | if (line) |
712 | free(line); | |
9a93d992 SH |
713 | fclose(f); |
714 | return ret; | |
715 | } | |
716 | ||
b98f7d6e SH |
717 | /* |
718 | * /etc/lxc/lxc.conf can contain lxc.cgroup.use = entries. | |
719 | * If any of those are present, then lxc will ONLY consider | |
720 | * cgroup filesystems mounted at one of the listed entries. | |
721 | */ | |
722 | static char *get_cgroup_uselist() | |
9a93d992 | 723 | { |
b98f7d6e SH |
724 | FILE *f; |
725 | char *line = NULL, *ret = NULL; | |
726 | size_t sz = 0, retsz = 0, newsz; | |
9a93d992 | 727 | |
b98f7d6e SH |
728 | if ((f = fopen(LXC_GLOBAL_CONF, "r")) == NULL) |
729 | return NULL; | |
730 | while (getline(&line, &sz, f) != -1) { | |
731 | char *p = line; | |
732 | while (*p && isblank(*p)) | |
733 | p++; | |
734 | if (strncmp(p, "lxc.cgroup.use", 14) != 0) | |
735 | continue; | |
736 | p = index(p, '='); | |
737 | if (!p) | |
738 | continue; | |
739 | p++; | |
740 | while (*p && isblank(*p)) | |
741 | p++; | |
742 | if (strlen(p) < 1) | |
743 | continue; | |
744 | newsz = retsz + strlen(p); | |
745 | if (retsz == 0) | |
746 | newsz += 1; // for trailing \0 | |
747 | // the last line in the file could lack \n | |
748 | if (p[strlen(p)-1] != '\n') | |
749 | newsz += 1; | |
750 | ret = realloc(ret, newsz); | |
751 | if (!ret) { | |
752 | ERROR("Out of memory reading cgroup uselist"); | |
753 | fclose(f); | |
754 | free(line); | |
755 | return (char *)-ENOMEM; | |
756 | } | |
757 | if (retsz == 0) | |
758 | strcpy(ret, p); | |
759 | else | |
760 | strcat(ret, p); | |
761 | if (p[strlen(p)-1] != '\n') | |
762 | ret[newsz-2] = '\0'; | |
763 | ret[newsz-1] = '\0'; | |
764 | retsz = newsz; | |
9a93d992 SH |
765 | } |
766 | ||
b98f7d6e SH |
767 | if (line) |
768 | free(line); | |
769 | return ret; | |
9a93d992 SH |
770 | } |
771 | ||
b98f7d6e | 772 | static bool is_in_uselist(char *uselist, struct mntent *m) |
9a93d992 | 773 | { |
b98f7d6e SH |
774 | char *p; |
775 | if (!uselist) | |
776 | return true; | |
777 | if (!*uselist) | |
778 | return false; | |
779 | while (*uselist) { | |
780 | p = index(uselist, '\n'); | |
781 | if (strncmp(m->mnt_dir, uselist, p - uselist) == 0) | |
782 | return true; | |
783 | uselist = p+1; | |
9a93d992 | 784 | } |
b98f7d6e | 785 | return false; |
9a93d992 SH |
786 | } |
787 | ||
b98f7d6e | 788 | static bool find_real_cgroup(struct cgroup_desc *d, char *path) |
9a93d992 | 789 | { |
b98f7d6e SH |
790 | FILE *f; |
791 | char *line = NULL, *p, *p2; | |
792 | int ret = 0; | |
793 | size_t len; | |
9a93d992 | 794 | |
b98f7d6e SH |
795 | if ((f = fopen("/proc/self/cgroup", "r")) == NULL) { |
796 | SYSERROR("Error opening /proc/self/cgroups"); | |
797 | return false; | |
798 | } | |
799 | ||
800 | // If there is no subsystem, ignore the mount. Note we may want | |
801 | // to change this, so that unprivileged users can use a unbound | |
802 | // cgroup mount to arrange their container tasks. | |
803 | if (!d->subsystems) { | |
804 | fclose(f); | |
805 | return false; | |
806 | } | |
807 | while (getline(&line, &len, f) != -1) { | |
808 | if (!(p = index(line, ':'))) | |
9a93d992 | 809 | continue; |
b98f7d6e | 810 | if (!(p2 = index(++p, ':'))) |
9a93d992 | 811 | continue; |
b98f7d6e | 812 | *p2 = '\0'; |
4f17323e CS |
813 | // remove trailing newlines |
814 | if (*(p2 + 1) && p2[strlen(p2 + 1)] == '\n') | |
815 | p2[strlen(p2 + 1)] = '\0'; | |
b98f7d6e SH |
816 | // in case of multiple mounts it may be more correct to |
817 | // insist all subsystems be the same | |
4f17323e | 818 | if (subsys_lists_match(p, d->subsystems)) |
b98f7d6e SH |
819 | goto found; |
820 | } | |
9a93d992 | 821 | |
b98f7d6e SH |
822 | if (line) |
823 | free(line); | |
824 | fclose(f); | |
825 | return false;; | |
826 | ||
827 | found: | |
828 | fclose(f); | |
829 | ret = snprintf(path, MAXPATHLEN, "%s", p2+1); | |
830 | if (ret < 0 || ret >= MAXPATHLEN) { | |
831 | free(line); | |
832 | return false; | |
833 | } | |
834 | free(line); | |
835 | return true; | |
9a93d992 SH |
836 | } |
837 | ||
b98f7d6e | 838 | |
ae5c8b8e | 839 | /* |
b98f7d6e SH |
840 | * for a given cgroup mount entry, and a to-be-created container, |
841 | * 1. Figure out full path of the cgroup we are currently in, | |
842 | * 2. Find a new free cgroup which is $path / $lxc_name with an | |
843 | * optional '-$n' where n is an ever-increasing integer. | |
ae5c8b8e | 844 | */ |
b98f7d6e | 845 | static char *find_free_cgroup(struct cgroup_desc *d, const char *lxc_name) |
460a1cf0 | 846 | { |
b98f7d6e SH |
847 | char tail[20], cgpath[MAXPATHLEN], *cgp, path[MAXPATHLEN]; |
848 | int i = 0, ret; | |
849 | size_t l; | |
fd37327f | 850 | |
b98f7d6e SH |
851 | if (!find_real_cgroup(d, cgpath)) { |
852 | ERROR("Failed to find current cgroup"); | |
853 | return NULL; | |
460a1cf0 DW |
854 | } |
855 | ||
b98f7d6e SH |
856 | /* |
857 | * If d->mntpt is '/a/b/c/d', and the mountpoint is /x/y/z, | |
858 | * then look for ourselves in: | |
859 | * /x/y/z/a/b/c/d/tasks | |
860 | * /x/y/z/b/c/d/tasks | |
861 | * /x/y/z/c/d/tasks | |
862 | * /x/y/z/d/tasks | |
863 | * /x/y/z/tasks | |
864 | */ | |
865 | cgp = cgpath; | |
866 | while (cgp[0]) { | |
867 | ret = snprintf(path, MAXPATHLEN, "%s%s/tasks", d->mntpt, cgp); | |
ae5c8b8e | 868 | if (ret < 0 || ret >= MAXPATHLEN) |
b98f7d6e SH |
869 | return NULL; |
870 | if (!is_in_cgroup(getpid(), path)) { | |
871 | // does not exist, try the next one | |
872 | cgp = index(cgp+1, '/'); | |
873 | if (!cgp) | |
874 | break; | |
875 | continue; | |
c8f7c563 | 876 | } |
b98f7d6e SH |
877 | break; |
878 | } | |
879 | if (!cgp || !*cgp) { | |
880 | // try just the path | |
881 | ret = snprintf(path, MAXPATHLEN, "%s/tasks", d->mntpt); | |
882 | if (ret < 0 || ret >= MAXPATHLEN) | |
883 | return NULL; | |
884 | if (!is_in_cgroup(getpid(), path)) | |
885 | return NULL; | |
886 | } | |
887 | // found it | |
888 | // path has '/tasks' at end, drop that | |
c32981c3 | 889 | if (!(cgp = strrchr(path, '/'))) { |
b98f7d6e SH |
890 | ERROR("Got nonsensical path name %s\n", path); |
891 | return NULL; | |
892 | } | |
893 | *cgp = '\0'; | |
c8f7c563 | 894 | |
b98f7d6e SH |
895 | if (strlen(path) + strlen(lxc_name) + 20 > MAXPATHLEN) { |
896 | ERROR("Error: cgroup path too long"); | |
897 | return NULL; | |
898 | } | |
899 | tail[0] = '\0'; | |
900 | while (1) { | |
901 | struct stat sb; | |
902 | int freebytes = MAXPATHLEN - (cgp - path); | |
903 | ||
904 | if (i) { | |
905 | ret = snprintf(tail, 20, "-%d", i); | |
906 | if (ret < 0 || ret >= 20) | |
907 | return NULL; | |
908 | } | |
909 | ret = snprintf(cgp, freebytes, "/%s%s", lxc_name, tail); | |
910 | if (ret < 0 || ret >= freebytes) | |
911 | return NULL; | |
912 | if (stat(path, &sb) == -1) | |
913 | break; | |
914 | i++; | |
c8f7c563 CS |
915 | } |
916 | ||
b98f7d6e SH |
917 | l = strlen(cgpath); |
918 | ret = snprintf(cgpath + l, MAXPATHLEN - l, "/%s%s", lxc_name, tail); | |
919 | if (ret < 0 || ret >= (MAXPATHLEN - l)) { | |
920 | ERROR("Out of memory"); | |
921 | return NULL; | |
922 | } | |
923 | if ((d->realcgroup = strdup(cgpath)) == NULL) { | |
924 | ERROR("Out of memory"); | |
925 | return NULL; | |
926 | } | |
927 | l = strlen(d->realcgroup); | |
928 | if (l > 0 && d->realcgroup[l-1] == '\n') | |
929 | d->realcgroup[l-1] = '\0'; | |
930 | return strdup(path); | |
c8f7c563 CS |
931 | } |
932 | ||
d08ba6ec | 933 | /* |
ae5c8b8e SH |
934 | * For a new container, find a cgroup path which is unique in all cgroup mounts. |
935 | * I.e. if r1 is already running, then /lxc/r1-1 may be used. | |
936 | * | |
937 | * @lxcgroup: the cgroup 'group' the contaienr should run in. By default, this | |
938 | * is just 'lxc'. Admins may wish to group some containers into other groups, | |
939 | * i.e. 'build', to take advantage of cgroup hierarchy to simplify group | |
940 | * administration. Also, unprivileged users who are placed into a cgroup by | |
941 | * libcgroup_pam will be using that cgroup rather than the system-wide 'lxc' | |
942 | * group. | |
943 | * @name: the name of the container | |
944 | * | |
945 | * The chosen cgpath is returned as a strdup'd string. The caller will have to | |
946 | * free that eventually, however the lxc monitor will keep that string so as to | |
947 | * return it in response to a LXC_COMMAND_CGROUP query. | |
948 | * | |
23622a2a SH |
949 | * Note the path is relative to cgroup mounts. I.e. if the freezer subsystem |
950 | * is at /sys/fs/cgroup/freezer, and this fn returns '/lxc/r1', then the | |
951 | * freezer cgroup's full path will be /sys/fs/cgroup/freezer/lxc/r1/. | |
ae5c8b8e | 952 | * |
ae5c8b8e | 953 | * Races won't be determintal, you'll just end up with leftover unused cgroups |
d08ba6ec | 954 | */ |
b98f7d6e | 955 | struct cgroup_desc *lxc_cgroup_path_create(const char *name) |
d08ba6ec | 956 | { |
b98f7d6e | 957 | struct cgroup_desc *retdesc = NULL, *newdesc = NULL; |
ae5c8b8e | 958 | FILE *file = NULL; |
93d564ed | 959 | struct mntent mntent_r; |
fd37327f | 960 | char buf[LARGE_MAXPATHLEN] = {0}; |
b98f7d6e SH |
961 | char *all_subsystems = get_all_subsystems(); |
962 | char *cgroup_uselist = get_cgroup_uselist(); | |
d08ba6ec | 963 | |
b98f7d6e SH |
964 | if (cgroup_uselist == (char *)-ENOMEM) { |
965 | if (all_subsystems) | |
966 | free(all_subsystems); | |
967 | return NULL; | |
968 | } | |
969 | if (!all_subsystems) { | |
970 | ERROR("failed to get a list of all cgroup subsystems"); | |
971 | if (cgroup_uselist) | |
972 | free(cgroup_uselist); | |
9a93d992 | 973 | return NULL; |
9a93d992 | 974 | } |
ae5c8b8e SH |
975 | file = setmntent(MTAB, "r"); |
976 | if (!file) { | |
977 | SYSERROR("failed to open %s", MTAB); | |
b98f7d6e SH |
978 | free(all_subsystems); |
979 | if (cgroup_uselist) | |
980 | free(cgroup_uselist); | |
981 | return NULL; | |
d08ba6ec | 982 | } |
1ac470c0 | 983 | |
93d564ed | 984 | while ((getmntent_r(file, &mntent_r, buf, sizeof(buf)))) { |
fd4f5a56 | 985 | |
fd37327f | 986 | if (strcmp(mntent_r.mnt_type, "cgroup")) |
ae5c8b8e | 987 | continue; |
b98f7d6e SH |
988 | |
989 | if (cgroup_uselist && !is_in_uselist(cgroup_uselist, &mntent_r)) | |
ae5c8b8e | 990 | continue; |
e4659536 | 991 | |
9a93d992 | 992 | /* make sure we haven't checked this subsystem already */ |
b98f7d6e | 993 | if (is_in_desclist(retdesc, mntent_r.mnt_opts, all_subsystems)) |
9a93d992 | 994 | continue; |
9a93d992 | 995 | |
b98f7d6e SH |
996 | if (!(newdesc = malloc(sizeof(struct cgroup_desc)))) { |
997 | ERROR("Out of memory reading cgroups"); | |
ae5c8b8e | 998 | goto fail; |
b98f7d6e SH |
999 | } |
1000 | newdesc->subsystems = record_visited(mntent_r.mnt_opts, all_subsystems); | |
1001 | if (newdesc->subsystems == (char *)-ENOMEM) { | |
1002 | ERROR("Out of memory recording cgroup subsystems"); | |
1003 | free(newdesc); | |
1004 | newdesc = NULL; | |
1005 | goto fail; | |
1006 | } | |
1007 | if (!newdesc->subsystems) { | |
1008 | free(newdesc); | |
1009 | newdesc = NULL; | |
1010 | continue; | |
1011 | } | |
1012 | newdesc->mntpt = strdup(mntent_r.mnt_dir); | |
1013 | newdesc->realcgroup = NULL; | |
1014 | newdesc->curcgroup = find_free_cgroup(newdesc, name); | |
1015 | if (!newdesc->mntpt || !newdesc->curcgroup) { | |
1016 | ERROR("Out of memory reading cgroups"); | |
1017 | goto fail; | |
1018 | } | |
5193cc3d | 1019 | |
b98f7d6e | 1020 | set_clone_children(&mntent_r); |
fd4f5a56 | 1021 | |
b98f7d6e SH |
1022 | if (mkdir(newdesc->curcgroup, 0755)) { |
1023 | ERROR("Error creating cgroup %s", newdesc->curcgroup); | |
ae5c8b8e | 1024 | goto fail; |
e7f40d8a | 1025 | } |
b98f7d6e SH |
1026 | newdesc->next = retdesc; |
1027 | retdesc = newdesc; | |
d08ba6ec SH |
1028 | } |
1029 | ||
ae5c8b8e | 1030 | endmntent(file); |
b98f7d6e SH |
1031 | free(all_subsystems); |
1032 | if (cgroup_uselist) | |
1033 | free(cgroup_uselist); | |
1034 | return retdesc; | |
ae5c8b8e SH |
1035 | |
1036 | fail: | |
1037 | endmntent(file); | |
b98f7d6e SH |
1038 | free(all_subsystems); |
1039 | if (cgroup_uselist) | |
1040 | free(cgroup_uselist); | |
1041 | if (newdesc) { | |
1042 | if (newdesc->mntpt) | |
1043 | free(newdesc->mntpt); | |
1044 | if (newdesc->subsystems) | |
1045 | free(newdesc->subsystems); | |
1046 | if (newdesc->curcgroup) | |
1047 | free(newdesc->curcgroup); | |
1048 | if (newdesc->realcgroup) | |
1049 | free(newdesc->realcgroup); | |
1050 | free(newdesc); | |
1051 | } | |
1052 | while (retdesc) { | |
1053 | struct cgroup_desc *t = retdesc; | |
1054 | retdesc = retdesc->next; | |
1055 | if (t->mntpt) | |
1056 | free(t->mntpt); | |
1057 | if (t->subsystems) | |
1058 | free(t->subsystems); | |
1059 | if (t->curcgroup) | |
1060 | free(t->curcgroup); | |
1061 | if (t->realcgroup) | |
1062 | free(t->realcgroup); | |
1063 | free(t); | |
1064 | ||
1065 | } | |
ae5c8b8e | 1066 | return NULL; |
36b86299 DL |
1067 | } |
1068 | ||
b98f7d6e | 1069 | static bool lxc_cgroup_enter_one(const char *dir, int pid) |
36b86299 | 1070 | { |
23622a2a | 1071 | char path[MAXPATHLEN]; |
b98f7d6e SH |
1072 | int ret; |
1073 | FILE *fout; | |
ef342abb | 1074 | |
b98f7d6e SH |
1075 | ret = snprintf(path, MAXPATHLEN, "%s/tasks", dir); |
1076 | if (ret < 0 || ret >= MAXPATHLEN) { | |
1077 | ERROR("Error entering cgroup"); | |
1078 | return false; | |
ef342abb | 1079 | } |
b98f7d6e SH |
1080 | fout = fopen(path, "w"); |
1081 | if (!fout) { | |
1082 | SYSERROR("Error entering cgroup"); | |
1083 | return false; | |
1084 | } | |
1085 | if (fprintf(fout, "%d\n", (int)pid) < 0) { | |
1086 | ERROR("Error writing pid to %s to enter cgroup", path); | |
1087 | fclose(fout); | |
1088 | return false; | |
1089 | } | |
1090 | if (fclose(fout) < 0) { | |
1091 | SYSERROR("Error writing pid to %s to enter cgroup", path); | |
1092 | return false; | |
ae5c8b8e | 1093 | } |
f0e64b8b | 1094 | |
b98f7d6e SH |
1095 | return true; |
1096 | } | |
1097 | ||
1098 | int lxc_cgroup_enter(struct cgroup_desc *cgroups, pid_t pid) | |
1099 | { | |
1100 | while (cgroups) { | |
1101 | if (!cgroups->subsystems) | |
1102 | goto next; | |
1103 | ||
1104 | if (!lxc_cgroup_enter_one(cgroups->curcgroup, pid)) | |
1105 | return -1; | |
1106 | next: | |
1107 | cgroups = cgroups->next; | |
1108 | } | |
1109 | return 0; | |
bcbd102c SH |
1110 | } |
1111 | ||
60bf62d4 | 1112 | static int cgroup_rmdir(char *dirname) |
341a9bd8 SH |
1113 | { |
1114 | struct dirent dirent, *direntp; | |
341a9bd8 SH |
1115 | DIR *dir; |
1116 | int ret; | |
1117 | char pathname[MAXPATHLEN]; | |
1118 | ||
1119 | dir = opendir(dirname); | |
1120 | if (!dir) { | |
1121 | WARN("failed to open directory: %m"); | |
1122 | return -1; | |
1123 | } | |
1124 | ||
341a9bd8 SH |
1125 | while (!readdir_r(dir, &dirent, &direntp)) { |
1126 | struct stat mystat; | |
9ba8130c | 1127 | int rc; |
341a9bd8 SH |
1128 | |
1129 | if (!direntp) | |
1130 | break; | |
1131 | ||
1132 | if (!strcmp(direntp->d_name, ".") || | |
b98f7d6e | 1133 | !strcmp(direntp->d_name, "..")) |
341a9bd8 SH |
1134 | continue; |
1135 | ||
9ba8130c SH |
1136 | rc = snprintf(pathname, MAXPATHLEN, "%s/%s", dirname, direntp->d_name); |
1137 | if (rc < 0 || rc >= MAXPATHLEN) { | |
1138 | ERROR("pathname too long"); | |
1139 | continue; | |
1140 | } | |
341a9bd8 SH |
1141 | ret = stat(pathname, &mystat); |
1142 | if (ret) | |
1143 | continue; | |
1144 | if (S_ISDIR(mystat.st_mode)) | |
60bf62d4 | 1145 | cgroup_rmdir(pathname); |
341a9bd8 SH |
1146 | } |
1147 | ||
1148 | ret = rmdir(dirname); | |
1149 | ||
1150 | if (closedir(dir)) | |
1151 | ERROR("failed to close directory"); | |
1152 | return ret; | |
341a9bd8 | 1153 | } |
bcbd102c | 1154 | |
bcbd102c SH |
1155 | /* |
1156 | * for each mounted cgroup, destroy the cgroup for the container | |
1157 | */ | |
b98f7d6e | 1158 | void lxc_cgroup_destroy_desc(struct cgroup_desc *cgroups) |
a6ddef61 | 1159 | { |
b98f7d6e SH |
1160 | while (cgroups) { |
1161 | struct cgroup_desc *next = cgroups->next; | |
1162 | if (cgroup_rmdir(cgroups->curcgroup) < 0) | |
1163 | SYSERROR("Error removing cgroup directory %s", cgroups->curcgroup); | |
1164 | free(cgroups->mntpt); | |
1165 | free(cgroups->subsystems); | |
1166 | free(cgroups->curcgroup); | |
1167 | free(cgroups->realcgroup); | |
1168 | free(cgroups); | |
1169 | cgroups = next; | |
bcbd102c | 1170 | } |
a6ddef61 MN |
1171 | } |
1172 | ||
ae5c8b8e | 1173 | int lxc_cgroup_attach(pid_t pid, const char *name, const char *lxcpath) |
576f946d | 1174 | { |
b98f7d6e | 1175 | FILE *f; |
4f17323e | 1176 | char *line = NULL, ret = 0; |
b98f7d6e SH |
1177 | size_t len = 0; |
1178 | int first = 1; | |
ef6e34ee | 1179 | char *dirpath; |
576f946d | 1180 | |
b98f7d6e SH |
1181 | /* read the list of subsystems from the kernel */ |
1182 | f = fopen("/proc/cgroups", "r"); | |
1183 | if (!f) | |
e14f67a7 | 1184 | return -1; |
b98f7d6e SH |
1185 | |
1186 | while (getline(&line, &len, f) != -1) { | |
1187 | char *c; | |
1188 | ||
1189 | /* skip the first line */ | |
1190 | if (first) { | |
1191 | first=0; | |
1192 | continue; | |
1193 | } | |
1194 | ||
1195 | c = strchr(line, '\t'); | |
1196 | if (!c) | |
1197 | continue; | |
1198 | *c = '\0'; | |
1199 | dirpath = lxc_cgroup_path_get(line, name, lxcpath); | |
1200 | if (!dirpath) | |
1201 | continue; | |
1202 | ||
1203 | INFO("joining pid %d to cgroup %s", pid, dirpath); | |
4f17323e | 1204 | if (!lxc_cgroup_enter_one(dirpath, pid)) { |
b98f7d6e | 1205 | ERROR("Failed joining %d to %s\n", pid, dirpath); |
4f17323e | 1206 | ret = -1; |
b98f7d6e SH |
1207 | continue; |
1208 | } | |
8b92dc3a | 1209 | } |
576f946d | 1210 | |
b98f7d6e SH |
1211 | if (line) |
1212 | free(line); | |
1213 | fclose(f); | |
ef6e34ee | 1214 | return ret; |
e0f888d9 | 1215 | } |
283678ed | 1216 | |
b98f7d6e | 1217 | bool is_in_subcgroup(int pid, const char *subsystem, struct cgroup_desc *d) |
283678ed SH |
1218 | { |
1219 | char filepath[MAXPATHLEN], *line = NULL, v1[MAXPATHLEN], v2[MAXPATHLEN]; | |
1220 | FILE *f; | |
1221 | int ret, junk; | |
b98f7d6e | 1222 | size_t sz = 0, l1, l2; |
283678ed SH |
1223 | char *end = index(subsystem, '.'); |
1224 | int len = end ? (end - subsystem) : strlen(subsystem); | |
b98f7d6e SH |
1225 | const char *cgpath = NULL; |
1226 | ||
1227 | while (d) { | |
1228 | if (in_subsys_list("devices", d->subsystems)) { | |
1229 | cgpath = d->realcgroup; | |
1230 | l1 = strlen(cgpath); | |
1231 | break; | |
1232 | } | |
1233 | d = d->next; | |
1234 | } | |
1235 | if (!d) | |
1236 | return false; | |
283678ed SH |
1237 | |
1238 | ret = snprintf(filepath, MAXPATHLEN, "/proc/%d/cgroup", pid); | |
1239 | if (ret < 0 || ret >= MAXPATHLEN) | |
1240 | return false; | |
1241 | if ((f = fopen(filepath, "r")) == NULL) | |
1242 | return false; | |
1243 | while (getline(&line, &sz, f) != -1) { | |
1244 | // nr:subsystem:path | |
1245 | v2[0] = v2[1] = '\0'; | |
1246 | ret = sscanf(line, "%d:%[^:]:%s", &junk, v1, v2); | |
1247 | if (ret != 3) { | |
1248 | fclose(f); | |
b98f7d6e | 1249 | free(line); |
283678ed SH |
1250 | return false; |
1251 | } | |
1252 | len = end ? end - subsystem : strlen(subsystem); | |
1253 | if (strncmp(v1, subsystem, len) != 0) | |
1254 | continue; | |
1255 | // v2 will start with '/', skip it by using v2+1 | |
1256 | // we must be in SUBcgroup, so make sure l2 > l1 | |
1257 | l2 = strlen(v2+1); | |
1258 | if (l2 > l1 && strncmp(v2+1, cgpath, l1) == 0) { | |
1259 | fclose(f); | |
b98f7d6e | 1260 | free(line); |
283678ed SH |
1261 | return true; |
1262 | } | |
1263 | } | |
1264 | fclose(f); | |
b98f7d6e SH |
1265 | if (line) |
1266 | free(line); | |
283678ed SH |
1267 | return false; |
1268 | } | |
b113383b | 1269 | |
b98f7d6e | 1270 | char *cgroup_get_subsys_path(struct lxc_handler *handler, const char *subsys) |
b113383b | 1271 | { |
b98f7d6e | 1272 | struct cgroup_desc *d; |
b113383b | 1273 | |
b98f7d6e SH |
1274 | for (d = handler->cgroup; d; d = d->next) { |
1275 | if (in_subsys_list(subsys, d->subsystems)) | |
1276 | return d->realcgroup; | |
1277 | } | |
b113383b | 1278 | |
b98f7d6e SH |
1279 | return NULL; |
1280 | } | |
1281 | ||
1282 | static int _setup_cgroup(struct lxc_handler *h, struct lxc_list *cgroups, | |
1283 | int devices) | |
1284 | { | |
1285 | struct lxc_list *iterator; | |
1286 | struct lxc_cgroup *cg; | |
1287 | int ret = -1; | |
1288 | ||
1289 | if (lxc_list_empty(cgroups)) | |
1290 | return 0; | |
1291 | ||
1292 | lxc_list_for_each(iterator, cgroups) { | |
1293 | cg = iterator->elem; | |
1294 | ||
1295 | if (devices == !strncmp("devices", cg->subsystem, 7)) { | |
1296 | if (strcmp(cg->subsystem, "devices.deny") == 0 && | |
1297 | cgroup_devices_has_deny(h, cg->value)) | |
1298 | continue; | |
1299 | if (strcmp(cg->subsystem, "devices.allow") == 0 && | |
1300 | cgroup_devices_has_allow(h, cg->value)) | |
1301 | continue; | |
1302 | if (lxc_cgroup_set_value(h, cg->subsystem, cg->value)) { | |
1303 | ERROR("Error setting %s to %s for %s\n", | |
1304 | cg->subsystem, cg->value, h->name); | |
1305 | goto out; | |
1306 | } | |
b113383b | 1307 | } |
b98f7d6e SH |
1308 | |
1309 | DEBUG("cgroup '%s' set to '%s'", cg->subsystem, cg->value); | |
b113383b SH |
1310 | } |
1311 | ||
b98f7d6e SH |
1312 | ret = 0; |
1313 | INFO("cgroup has been setup"); | |
1314 | out: | |
b113383b SH |
1315 | return ret; |
1316 | } | |
b98f7d6e SH |
1317 | |
1318 | int setup_cgroup_devices(struct lxc_handler *h, struct lxc_list *cgroups) | |
1319 | { | |
1320 | return _setup_cgroup(h, cgroups, 1); | |
1321 | } | |
1322 | ||
1323 | int setup_cgroup(struct lxc_handler *h, struct lxc_list *cgroups) | |
1324 | { | |
1325 | return _setup_cgroup(h, cgroups, 0); | |
1326 | } |