]> git.proxmox.com Git - mirror_lxcfs.git/blame - lxcfs.c
Add support for file read
[mirror_lxcfs.git] / lxcfs.c
CommitLineData
758ad80c
SH
1/* lxcfs
2 *
3 * Copyright © 2014 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2, as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20/*
21 * NOTES - make sure to run this as -s to avoid threading.
22 * TODO - can we enforce that here from the code?
23 */
24#define FUSE_USE_VERSION 26
25
2183082c 26#include <stdio.h>
758ad80c
SH
27#include <dirent.h>
28#include <fcntl.h>
29#include <fuse.h>
30#include <unistd.h>
31#include <errno.h>
32#include <stdbool.h>
33#include <time.h>
34#include <string.h>
35#include <stdlib.h>
36#include <libgen.h>
37
38#include <nih/alloc.h>
39#include <nih/string.h>
40
41#include "cgmanager.h"
42
43struct lxcfs_state {
44 /*
45 * a null-terminated, nih-allocated list of the mounted subsystems. We
46 * detect this at startup.
47 */
48 char **subsystems;
49};
50#define LXCFS_DATA ((struct lxcfs_state *) fuse_get_context()->private_data)
51
053a659d
SH
52/*
53 * Given a open file * to /proc/pid/{u,g}id_map, and an id
54 * valid in the caller's namespace, return the id mapped into
55 * pid's namespace.
56 * Returns the mapped id, or -1 on error.
57 */
58unsigned int
59convert_id_to_ns(FILE *idfile, unsigned int in_id)
60{
61 unsigned int nsuid, // base id for a range in the idfile's namespace
62 hostuid, // base id for a range in the caller's namespace
63 count; // number of ids in this range
64 char line[400];
65 int ret;
66
67 fseek(idfile, 0L, SEEK_SET);
68 while (fgets(line, 400, idfile)) {
69 ret = sscanf(line, "%u %u %u\n", &nsuid, &hostuid, &count);
70 if (ret != 3)
71 continue;
72 if (hostuid + count < hostuid || nsuid + count < nsuid) {
73 /*
74 * uids wrapped around - unexpected as this is a procfile,
75 * so just bail.
76 */
77 fprintf(stderr, "pid wrapparound at entry %u %u %u in %s",
78 nsuid, hostuid, count, line);
79 return -1;
80 }
81 if (hostuid <= in_id && hostuid+count > in_id) {
82 /*
83 * now since hostuid <= in_id < hostuid+count, and
84 * hostuid+count and nsuid+count do not wrap around,
85 * we know that nsuid+(in_id-hostuid) which must be
86 * less that nsuid+(count) must not wrap around
87 */
88 return (in_id - hostuid) + nsuid;
89 }
90 }
91
92 // no answer found
93 return -1;
94}
95
758ad80c
SH
96static bool is_privileged_over(pid_t pid, uid_t uid, uid_t victim)
97{
053a659d
SH
98 nih_local char *fpath = NULL;
99 bool answer = false;
100 uid_t nsuid;
101
758ad80c
SH
102 if (uid == victim)
103 return true;
104
105 /* check /proc/pid/uid_map */
053a659d
SH
106 fpath = NIH_MUST( nih_sprintf(NULL, "/proc/%d/uid_map", pid) );
107 FILE *f = fopen(fpath, "r");
108 if (!f)
109 return false;
110
111 nsuid = convert_id_to_ns(f, uid);
112 if (nsuid)
113 goto out;
114
115 nsuid = convert_id_to_ns(f, victim);
116 if (nsuid == -1)
117 goto out;
118
119 answer = true;
120
121out:
122 fclose(f);
123 return answer;
758ad80c
SH
124}
125
126static bool perms_include(int fmode, mode_t req_mode)
127{
128 fprintf(stderr, "perms_include: checking whether %d includes %d\n",
129 fmode, req_mode);
130 return (fmode & req_mode) == req_mode;
131}
132
133/*
134 * check whether a fuse context may access a cgroup dir or file
135 *
136 * If file is not null, it is a cgroup file to check under cg.
137 * If file is null, then we are checking perms on cg itself.
138 *
139 * For files we can check the mode of the list_keys result.
140 * For cgroups, we must make assumptions based on the files under the
141 * cgroup, because cgmanager doesn't tell us ownership/perms of cgroups
142 * yet.
143 */
144static bool fc_may_access(struct fuse_context *fc, const char *contrl, const char *cg, const char *file, mode_t mode)
145{
146 nih_local struct cgm_keys **list = NULL;
147 int i;
148
149 if (!file)
150 file = "tasks";
151
152 if (*file == '/')
153 file++;
154
155 if (!cgm_list_keys(contrl, cg, &list))
156 return false;
157 for (i = 0; list[i]; i++) {
158 if (strcmp(list[i]->name, file) == 0) {
159 struct cgm_keys *k = list[i];
758ad80c
SH
160 if (is_privileged_over(fc->pid, fc->uid, k->uid)) {
161 if (perms_include(k->mode >> 6, mode))
162 return true;
163 }
164 if (fc->gid == k->gid) {
165 if (perms_include(k->mode >> 3, mode))
166 return true;
167 }
168 return perms_include(k->mode, mode);
169 }
170 }
171
172 return false;
173}
174
175/*
176 * given /cgroup/freezer/a/b, return "freezer". this will be nih-allocated
177 * and needs to be nih_freed.
178 */
179static char *pick_controller_from_path(struct fuse_context *fc, const char *path)
180{
181 const char *p1;
182 char *ret, *slash;
183
184 if (strlen(path) < 9)
185 return NULL;
186 p1 = path+8;
187 ret = nih_strdup(NULL, p1);
188 if (!ret)
189 return ret;
190 slash = strstr(ret, "/");
191 if (slash)
192 *slash = '\0';
193
194 /* verify that it is a subsystem */
195 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
196 int i;
197 if (!list) {
198 nih_free(ret);
199 return NULL;
200 }
201 for (i = 0; list[i]; i++) {
202 if (strcmp(list[i], ret) == 0)
203 return ret;
204 }
205 nih_free(ret);
206 return NULL;
207}
208
209/*
210 * Find the start of cgroup in /cgroup/controller/the/cgroup/path
211 * Note that the returned value may include files (keynames) etc
212 */
213static const char *find_cgroup_in_path(const char *path)
214{
215 const char *p1;
216
217 if (strlen(path) < 9)
218 return NULL;
219 p1 = strstr(path+8, "/");
220 if (!p1)
221 return NULL;
222 return p1+1;
223}
224
225static bool is_child_cgroup(const char *contr, const char *dir, const char *f)
226{
227 nih_local char **list = NULL;
228 int i;
229
230 if (!f)
231 return false;
232 if (*f == '/')
233 f++;
234
235 if (!cgm_list_children(contr, dir, &list))
236 return false;
237 for (i = 0; list[i]; i++) {
238 if (strcmp(list[i], f) == 0)
239 return true;
240 }
241
242 return false;
243}
244
245static struct cgm_keys *get_cgroup_key(const char *contr, const char *dir, const char *f)
246{
247 nih_local struct cgm_keys **list = NULL;
248 struct cgm_keys *k;
249 int i;
250
251 if (!f)
252 return NULL;
253 if (*f == '/')
254 f++;
255 if (!cgm_list_keys(contr, dir, &list))
256 return NULL;
257 for (i = 0; list[i]; i++) {
258 if (strcmp(list[i]->name, f) == 0) {
259 k = NIH_MUST( nih_alloc(NULL, (sizeof(*k))) );
260 k->name = NIH_MUST( nih_strdup(k, list[i]->name) );
261 k->uid = list[i]->uid;
262 k->gid = list[i]->gid;
263 k->mode = list[i]->mode;
264 return k;
265 }
266 }
267
268 return NULL;
269}
270
271static void get_cgdir_and_path(const char *cg, char **dir, char **file)
272{
758ad80c
SH
273 char *p;
274
275 *dir = NIH_MUST( nih_strdup(NULL, cg) );
276 *file = strrchr(cg, '/');
277 if (!*file) {
278 *file = NULL;
279 return;
280 }
281 p = strrchr(*dir, '/');
282 *p = '\0';
283}
284
99978832
SH
285static size_t get_file_size(const char *contrl, const char *cg, const char *f)
286{
287 nih_local char *data = NULL;
288 size_t s;
289 if (!cgm_get_value(contrl, cg, f, &data))
290 return -EINVAL;
291 s = strlen(data);
292 return s;
293}
758ad80c
SH
294/*
295 * gettattr fn for anything under /cgroup
296 */
297static int cg_getattr(const char *path, struct stat *sb)
298{
299 struct timespec now;
300 struct fuse_context *fc = fuse_get_context();
301 nih_local char * cgdir = NULL;
302 char *fpath = NULL, *path1, *path2;
303 nih_local struct cgm_keys *k = NULL;
304 const char *cgroup;
305 nih_local char *controller = NULL;
306
307
308 if (!fc)
309 return -EIO;
310
311 memset(sb, 0, sizeof(struct stat));
312
313 if (clock_gettime(CLOCK_REALTIME, &now) < 0)
314 return -EINVAL;
315
316 sb->st_uid = sb->st_gid = 0;
317 sb->st_atim = sb->st_mtim = sb->st_ctim = now;
318 sb->st_size = 0;
319
320 if (strcmp(path, "/cgroup") == 0) {
321 sb->st_mode = S_IFDIR | 00755;
322 sb->st_nlink = 2;
323 return 0;
324 }
325
326 controller = pick_controller_from_path(fc, path);
327 if (!controller)
328 return -EIO;
758ad80c
SH
329 cgroup = find_cgroup_in_path(path);
330 if (!cgroup) {
331 /* this is just /cgroup/controller, return it as a dir */
332 sb->st_mode = S_IFDIR | 00755;
333 sb->st_nlink = 2;
334 return 0;
335 }
336
758ad80c
SH
337 get_cgdir_and_path(cgroup, &cgdir, &fpath);
338
339 if (!fpath) {
340 path1 = "/";
341 path2 = cgdir;
342 } else {
343 path1 = cgdir;
344 path2 = fpath;
345 }
346
758ad80c
SH
347 /* check that cgcopy is either a child cgroup of cgdir, or listed in its keys.
348 * Then check that caller's cgroup is under path if fpath is a child
349 * cgroup, or cgdir if fpath is a file */
350
351 if (is_child_cgroup(controller, path1, path2)) {
352 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
353 return -EPERM;
354
053a659d
SH
355 // get uid, gid, from '/tasks' file and make up a mode
356 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
357 sb->st_mode = S_IFDIR | 00755;
358 k = get_cgroup_key(controller, cgroup, "tasks");
359 if (!k) {
360 fprintf(stderr, "Failed to find a tasks file for %s\n", cgroup);
361 sb->st_uid = sb->st_gid = 0;
362 } else {
363 fprintf(stderr, "found a tasks file for %s\n", cgroup);
364 sb->st_uid = k->uid;
365 sb->st_gid = k->gid;
366 }
758ad80c
SH
367 sb->st_nlink = 2;
368 return 0;
369 }
370
371 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
372 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
373 return -EPERM;
374
758ad80c 375 sb->st_mode = S_IFREG | k->mode;
053a659d 376 sb->st_nlink = 1;
758ad80c
SH
377 sb->st_uid = k->uid;
378 sb->st_gid = k->gid;
99978832 379 sb->st_size = get_file_size(controller, path1, path2);
758ad80c
SH
380 return 0;
381 }
382
383 return -EINVAL;
384}
2183082c 385
758ad80c 386static int cg_opendir(const char *path, struct fuse_file_info *fi)
2183082c 387{
758ad80c
SH
388 return 0;
389}
390
391/*
392 * readdir function for anything under /cgroup
393 */
394static int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
395 struct fuse_file_info *fi)
396{
397 struct fuse_context *fc = fuse_get_context();
398
399 if (!fc)
400 return -EIO;
401
402 if (strcmp(path, "/cgroup") == 0) {
403 // get list of controllers
404 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
405 int i;
406
407 if (!list)
408 return -EIO;
409 /* TODO - collect the list of controllers at fuse_init */
410 for (i = 0; list[i]; i++) {
411 if (filler(buf, list[i], NULL, 0) != 0) {
412 return -EIO;
413 }
414 }
415 return 0;
416 }
417
418 // return list of keys for the controller, and list of child cgroups
419 nih_local struct cgm_keys **list = NULL;
420 const char *cgroup;
421 nih_local char *controller = NULL;
422 int i;
423
424 controller = pick_controller_from_path(fc, path);
425 if (!controller)
426 return -EIO;
427
428 cgroup = find_cgroup_in_path(path);
429 if (!cgroup) {
430 /* this is just /cgroup/controller, return its contents */
431 cgroup = "/";
432 }
433
434 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
435 return -EPERM;
436
437 if (!cgm_list_keys(controller, cgroup, &list))
438 return -EINVAL;
439 for (i = 0; list[i]; i++) {
440 fprintf(stderr, "adding key %s\n", list[i]->name);
441 if (filler(buf, list[i]->name, NULL, 0) != 0) {
442 return -EIO;
443 }
444 }
445
446 // now get the list of child cgroups
447 nih_local char **clist;
448
449 if (!cgm_list_children(controller, cgroup, &clist))
450 return 0;
451 for (i = 0; clist[i]; i++) {
452 fprintf(stderr, "adding child %s\n", clist[i]);
453 if (filler(buf, clist[i], NULL, 0) != 0) {
454 return -EIO;
455 }
456 }
457 return 0;
458}
459
460static int cg_releasedir(const char *path, struct fuse_file_info *fi)
461{
462 return 0;
463}
464
99978832
SH
465static int cg_open(const char *path, struct fuse_file_info *fi)
466{
467 nih_local char *controller = NULL;
468 const char *cgroup;
469 char *fpath = NULL, *path1, *path2;
470 nih_local char * cgdir = NULL;
471 nih_local struct cgm_keys *k = NULL;
472 struct fuse_context *fc = fuse_get_context();
473
474 if (!fc)
475 return -EIO;
476
477 controller = pick_controller_from_path(fc, path);
478 if (!controller)
479 return -EIO;
480 cgroup = find_cgroup_in_path(path);
481 if (!cgroup)
482 return -EINVAL;
483
484 get_cgdir_and_path(cgroup, &cgdir, &fpath);
485 if (!fpath) {
486 path1 = "/";
487 path2 = cgdir;
488 } else {
489 path1 = cgdir;
490 path2 = fpath;
491 }
492
493 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
494 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
495 return -EPERM;
496
497 /* TODO - we want to cache this info for read/write */
498 return 0;
499 }
500
501 return -EINVAL;
502}
503
504static int cg_read(const char *path, char *buf, size_t size, off_t offset,
505 struct fuse_file_info *fi)
506{
507 nih_local char *controller = NULL;
508 const char *cgroup;
509 char *fpath = NULL, *path1, *path2;
510 struct fuse_context *fc = fuse_get_context();
511 nih_local char * cgdir = NULL;
512 nih_local struct cgm_keys *k = NULL;
513
514 if (offset)
515 return -EIO;
516
517 if (!fc)
518 return -EIO;
519
520 controller = pick_controller_from_path(fc, path);
521 if (!controller)
522 return -EIO;
523 cgroup = find_cgroup_in_path(path);
524 if (!cgroup)
525 return -EINVAL;
526
527 get_cgdir_and_path(cgroup, &cgdir, &fpath);
528 if (!fpath) {
529 path1 = "/";
530 path2 = cgdir;
531 } else {
532 path1 = cgdir;
533 path2 = fpath;
534 }
535
536 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
537 nih_local char *data = NULL;
538 int s;
539
540 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
541 return -EPERM;
542
543 if (!cgm_get_value(controller, path1, path2, &data))
544 return -EINVAL;
545
546 s = strlen(data);
547 if (s > size)
548 s = size;
549 memcpy(buf, data, s);
550
551fprintf(stderr, "cg_read: returning %d: %s\n", s, buf);
552 return s;
553 }
554
555 return -EINVAL;
556}
557
758ad80c
SH
558/*
559 * So far I'm not actually using cg_ops and proc_ops, but listing them
560 * here makes it clearer who is supporting what. Still I prefer to
561 * call the real functions and not cg_ops->getattr.
562 */
563const struct fuse_operations cg_ops = {
564 .getattr = cg_getattr,
565 .readlink = NULL,
566 .getdir = NULL,
567 .mknod = NULL,
568 .mkdir = NULL,
569 .unlink = NULL,
570 .rmdir = NULL,
571 .symlink = NULL,
572 .rename = NULL,
573 .link = NULL,
574 .chmod = NULL,
575 .chown = NULL,
576 .truncate = NULL,
577 .utime = NULL,
99978832
SH
578 .open = cg_open,
579 .read = cg_read,
758ad80c
SH
580 .write = NULL,
581 .statfs = NULL,
582 .flush = NULL,
583 .release = NULL,
584 .fsync = NULL,
585
586 .setxattr = NULL,
587 .getxattr = NULL,
588 .listxattr = NULL,
589 .removexattr = NULL,
590
591 .opendir = cg_opendir,
592 .readdir = cg_readdir,
593 .releasedir = cg_releasedir,
594
595 .fsyncdir = NULL,
596 .init = NULL,
597 .destroy = NULL,
598 .access = NULL,
599 .create = NULL,
600 .ftruncate = NULL,
601 .fgetattr = NULL,
602};
603
604static int proc_getattr(const char *path, struct stat *sb)
605{
606 if (strcmp(path, "/proc") != 0)
607 return -EINVAL;
608 sb->st_mode = S_IFDIR | 00755;
609 sb->st_nlink = 2;
610 return 0;
611}
612
613const struct fuse_operations proc_ops = {
614 .getattr = proc_getattr,
615 .readlink = NULL,
616 .getdir = NULL,
617 .mknod = NULL,
618 .mkdir = NULL,
619 .unlink = NULL,
620 .rmdir = NULL,
621 .symlink = NULL,
622 .rename = NULL,
623 .link = NULL,
624 .chmod = NULL,
625 .chown = NULL,
626 .truncate = NULL,
627 .utime = NULL,
628 .open = NULL,
629 .read = NULL,
630 .write = NULL,
631 .statfs = NULL,
632 .flush = NULL,
633 .release = NULL,
634 .fsync = NULL,
635
636 .setxattr = NULL,
637 .getxattr = NULL,
638 .listxattr = NULL,
639 .removexattr = NULL,
640
641 .opendir = NULL,
642 .readdir = NULL,
643 .releasedir = NULL,
644
645 .fsyncdir = NULL,
646 .init = NULL,
647 .destroy = NULL,
648 .access = NULL,
649 .create = NULL,
650 .ftruncate = NULL,
651 .fgetattr = NULL,
652};
653
654static int lxcfs_getattr(const char *path, struct stat *sb)
655{
656 if (strcmp(path, "/") == 0) {
657 sb->st_mode = S_IFDIR | 00755;
658 sb->st_nlink = 2;
659 return 0;
660 }
661 if (strncmp(path, "/cgroup", 7) == 0) {
662 return cg_getattr(path, sb);
663 }
664 if (strncmp(path, "/proc", 7) == 0) {
665 return proc_getattr(path, sb);
666 }
667 return -EINVAL;
668}
669
670static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
671{
672 if (strcmp(path, "/") == 0)
673 return 0;
674
675 if (strncmp(path, "/cgroup", 7) == 0) {
676 return cg_opendir(path, fi);
677 }
678 return -EINVAL;
679}
680
681static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
682 struct fuse_file_info *fi)
683{
684 if (strcmp(path, "/") == 0) {
685 if (filler(buf, "proc", NULL, 0) != 0 ||
686 filler(buf, "cgroup", NULL, 0) != 0)
687 return -EINVAL;
688 return 0;
689 }
690 if (strncmp(path, "/cgroup", 7) == 0) {
691 return cg_readdir(path, buf, filler, offset, fi);
692 }
693 return -EINVAL;
694}
695
696static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
697{
698 if (strcmp(path, "/") == 0)
699 return 0;
700 if (strncmp(path, "/cgroup", 7) == 0) {
701 return cg_releasedir(path, fi);
702 }
703 return -EINVAL;
704}
705
99978832
SH
706static int lxcfs_open(const char *path, struct fuse_file_info *fi)
707{
708 if (strncmp(path, "/cgroup", 7) == 0) {
709 return cg_open(path, fi);
710 }
711
712 return -EINVAL;
713}
714
715static int lxcfs_read(const char *path, char *buf, size_t size, off_t offset,
716 struct fuse_file_info *fi)
717{
718 if (strncmp(path, "/cgroup", 7) == 0) {
719 return cg_read(path, buf, size, offset, fi);
720 }
721
722 return -EINVAL;
723}
724
725static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
726{
727 return 0;
728}
729
730static int lxcfs_release(const char *path, struct fuse_file_info *fi)
758ad80c 731{
99978832
SH
732 return 0;
733}
734
735static int lxcfs_fsync(const char *path, int datasync, struct fuse_file_info *fi)
736{
737 return 0;
758ad80c
SH
738}
739
740const struct fuse_operations lxcfs_ops = {
741 .getattr = lxcfs_getattr,
742 .readlink = NULL,
743 .getdir = NULL,
744 .mknod = NULL,
745 .mkdir = NULL,
746 .unlink = NULL,
747 .rmdir = NULL,
748 .symlink = NULL,
749 .rename = NULL,
750 .link = NULL,
751 .chmod = NULL,
752 .chown = NULL,
753 .truncate = NULL,
754 .utime = NULL,
99978832
SH
755
756 .open = lxcfs_open,
757 .read = lxcfs_read,
758 .release = lxcfs_release,
758ad80c 759 .write = NULL,
99978832 760
758ad80c 761 .statfs = NULL,
99978832
SH
762 .flush = lxcfs_flush,
763 .fsync = lxcfs_fsync,
758ad80c
SH
764
765 .setxattr = NULL,
766 .getxattr = NULL,
767 .listxattr = NULL,
768 .removexattr = NULL,
769
770 .opendir = lxcfs_opendir,
771 .readdir = lxcfs_readdir,
772 .releasedir = lxcfs_releasedir,
773
774 .fsyncdir = NULL,
775 .init = NULL,
776 .destroy = NULL,
777 .access = NULL,
778 .create = NULL,
779 .ftruncate = NULL,
780 .fgetattr = NULL,
781};
782
99978832 783static void usage(const char *me)
758ad80c
SH
784{
785 fprintf(stderr, "Usage:\n");
786 fprintf(stderr, "\n");
787 fprintf(stderr, "%s [FUSE and mount options] mountpoint\n", me);
788 exit(1);
789}
790
99978832 791static bool is_help(char *w)
758ad80c
SH
792{
793 if (strcmp(w, "-h") == 0 ||
794 strcmp(w, "--help") == 0 ||
795 strcmp(w, "-help") == 0 ||
796 strcmp(w, "help") == 0)
797 return true;
798 return false;
799}
800
801int main(int argc, char *argv[])
802{
803 int ret;
804 struct lxcfs_state *d;
805
806 if (argc < 2 || is_help(argv[1]))
807 usage(argv[0]);
808
809 d = malloc(sizeof(*d));
810 if (!d)
811 return -1;
812
813 if (!cgm_escape_cgroup())
814 fprintf(stderr, "WARNING: failed to escape to root cgroup\n");
815
816 if (!cgm_get_controllers(&d->subsystems))
817 return -1;
818
819 ret = fuse_main(argc, argv, &lxcfs_ops, d);
820
821 return ret;
2183082c 822}