]> git.proxmox.com Git - mirror_lxcfs.git/blob - lxcfs.c
implement write
[mirror_lxcfs.git] / lxcfs.c
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
26 #include <stdio.h>
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
43 struct 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
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 */
58 unsigned int
59 convert_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
96 static bool is_privileged_over(pid_t pid, uid_t uid, uid_t victim)
97 {
98 nih_local char *fpath = NULL;
99 bool answer = false;
100 uid_t nsuid;
101
102 if (uid == victim)
103 return true;
104
105 /* check /proc/pid/uid_map */
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
121 out:
122 fclose(f);
123 return answer;
124 }
125
126 static bool perms_include(int fmode, mode_t req_mode)
127 {
128 mode_t r;
129
130 switch (req_mode & O_ACCMODE) {
131 case O_RDONLY:
132 r = S_IROTH;
133 break;
134 case O_WRONLY:
135 r = S_IWOTH;
136 break;
137 case O_RDWR:
138 r = S_IROTH | S_IWOTH;
139 break;
140 default:
141 return false;
142 }
143 return ((fmode & r) == r);
144 }
145
146 /*
147 * check whether a fuse context may access a cgroup dir or file
148 *
149 * If file is not null, it is a cgroup file to check under cg.
150 * If file is null, then we are checking perms on cg itself.
151 *
152 * For files we can check the mode of the list_keys result.
153 * For cgroups, we must make assumptions based on the files under the
154 * cgroup, because cgmanager doesn't tell us ownership/perms of cgroups
155 * yet.
156 */
157 static bool fc_may_access(struct fuse_context *fc, const char *contrl, const char *cg, const char *file, mode_t mode)
158 {
159 nih_local struct cgm_keys **list = NULL;
160 int i;
161
162 if (!file)
163 file = "tasks";
164
165 if (*file == '/')
166 file++;
167
168 if (!cgm_list_keys(contrl, cg, &list))
169 return false;
170 for (i = 0; list[i]; i++) {
171 if (strcmp(list[i]->name, file) == 0) {
172 struct cgm_keys *k = list[i];
173 if (is_privileged_over(fc->pid, fc->uid, k->uid)) {
174 if (perms_include(k->mode >> 6, mode))
175 return true;
176 }
177 if (fc->gid == k->gid) {
178 if (perms_include(k->mode >> 3, mode))
179 return true;
180 }
181 return perms_include(k->mode, mode);
182 }
183 }
184
185 return false;
186 }
187
188 /*
189 * given /cgroup/freezer/a/b, return "freezer". this will be nih-allocated
190 * and needs to be nih_freed.
191 */
192 static char *pick_controller_from_path(struct fuse_context *fc, const char *path)
193 {
194 const char *p1;
195 char *ret, *slash;
196
197 if (strlen(path) < 9)
198 return NULL;
199 p1 = path+8;
200 ret = nih_strdup(NULL, p1);
201 if (!ret)
202 return ret;
203 slash = strstr(ret, "/");
204 if (slash)
205 *slash = '\0';
206
207 /* verify that it is a subsystem */
208 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
209 int i;
210 if (!list) {
211 nih_free(ret);
212 return NULL;
213 }
214 for (i = 0; list[i]; i++) {
215 if (strcmp(list[i], ret) == 0)
216 return ret;
217 }
218 nih_free(ret);
219 return NULL;
220 }
221
222 /*
223 * Find the start of cgroup in /cgroup/controller/the/cgroup/path
224 * Note that the returned value may include files (keynames) etc
225 */
226 static const char *find_cgroup_in_path(const char *path)
227 {
228 const char *p1;
229
230 if (strlen(path) < 9)
231 return NULL;
232 p1 = strstr(path+8, "/");
233 if (!p1)
234 return NULL;
235 return p1+1;
236 }
237
238 static bool is_child_cgroup(const char *contr, const char *dir, const char *f)
239 {
240 nih_local char **list = NULL;
241 int i;
242
243 if (!f)
244 return false;
245 if (*f == '/')
246 f++;
247
248 if (!cgm_list_children(contr, dir, &list))
249 return false;
250 for (i = 0; list[i]; i++) {
251 if (strcmp(list[i], f) == 0)
252 return true;
253 }
254
255 return false;
256 }
257
258 static struct cgm_keys *get_cgroup_key(const char *contr, const char *dir, const char *f)
259 {
260 nih_local struct cgm_keys **list = NULL;
261 struct cgm_keys *k;
262 int i;
263
264 if (!f)
265 return NULL;
266 if (*f == '/')
267 f++;
268 if (!cgm_list_keys(contr, dir, &list))
269 return NULL;
270 for (i = 0; list[i]; i++) {
271 if (strcmp(list[i]->name, f) == 0) {
272 k = NIH_MUST( nih_alloc(NULL, (sizeof(*k))) );
273 k->name = NIH_MUST( nih_strdup(k, list[i]->name) );
274 k->uid = list[i]->uid;
275 k->gid = list[i]->gid;
276 k->mode = list[i]->mode;
277 return k;
278 }
279 }
280
281 return NULL;
282 }
283
284 static void get_cgdir_and_path(const char *cg, char **dir, char **file)
285 {
286 char *p;
287
288 *dir = NIH_MUST( nih_strdup(NULL, cg) );
289 *file = strrchr(cg, '/');
290 if (!*file) {
291 *file = NULL;
292 return;
293 }
294 p = strrchr(*dir, '/');
295 *p = '\0';
296 }
297
298 static size_t get_file_size(const char *contrl, const char *cg, const char *f)
299 {
300 nih_local char *data = NULL;
301 size_t s;
302 if (!cgm_get_value(contrl, cg, f, &data))
303 return -EINVAL;
304 s = strlen(data);
305 return s;
306 }
307
308 /*
309 * FUSE ops for /cgroup
310 */
311
312 static int cg_getattr(const char *path, struct stat *sb)
313 {
314 struct timespec now;
315 struct fuse_context *fc = fuse_get_context();
316 nih_local char * cgdir = NULL;
317 char *fpath = NULL, *path1, *path2;
318 nih_local struct cgm_keys *k = NULL;
319 const char *cgroup;
320 nih_local char *controller = NULL;
321
322
323 if (!fc)
324 return -EIO;
325
326 memset(sb, 0, sizeof(struct stat));
327
328 if (clock_gettime(CLOCK_REALTIME, &now) < 0)
329 return -EINVAL;
330
331 sb->st_uid = sb->st_gid = 0;
332 sb->st_atim = sb->st_mtim = sb->st_ctim = now;
333 sb->st_size = 0;
334
335 if (strcmp(path, "/cgroup") == 0) {
336 sb->st_mode = S_IFDIR | 00755;
337 sb->st_nlink = 2;
338 return 0;
339 }
340
341 controller = pick_controller_from_path(fc, path);
342 if (!controller)
343 return -EIO;
344 cgroup = find_cgroup_in_path(path);
345 if (!cgroup) {
346 /* this is just /cgroup/controller, return it as a dir */
347 sb->st_mode = S_IFDIR | 00755;
348 sb->st_nlink = 2;
349 return 0;
350 }
351
352 get_cgdir_and_path(cgroup, &cgdir, &fpath);
353
354 if (!fpath) {
355 path1 = "/";
356 path2 = cgdir;
357 } else {
358 path1 = cgdir;
359 path2 = fpath;
360 }
361
362 /* check that cgcopy is either a child cgroup of cgdir, or listed in its keys.
363 * Then check that caller's cgroup is under path if fpath is a child
364 * cgroup, or cgdir if fpath is a file */
365
366 if (is_child_cgroup(controller, path1, path2)) {
367 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
368 return -EPERM;
369
370 // get uid, gid, from '/tasks' file and make up a mode
371 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
372 sb->st_mode = S_IFDIR | 00755;
373 k = get_cgroup_key(controller, cgroup, "tasks");
374 if (!k) {
375 fprintf(stderr, "Failed to find a tasks file for %s\n", cgroup);
376 sb->st_uid = sb->st_gid = 0;
377 } else {
378 fprintf(stderr, "found a tasks file for %s\n", cgroup);
379 sb->st_uid = k->uid;
380 sb->st_gid = k->gid;
381 }
382 sb->st_nlink = 2;
383 return 0;
384 }
385
386 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
387 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
388 return -EPERM;
389
390 sb->st_mode = S_IFREG | k->mode;
391 sb->st_nlink = 1;
392 sb->st_uid = k->uid;
393 sb->st_gid = k->gid;
394 sb->st_size = get_file_size(controller, path1, path2);
395 return 0;
396 }
397
398 return -ENOENT;
399 }
400
401 static int cg_opendir(const char *path, struct fuse_file_info *fi)
402 {
403 return 0;
404 }
405
406 static int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
407 struct fuse_file_info *fi)
408 {
409 struct fuse_context *fc = fuse_get_context();
410
411 if (!fc)
412 return -EIO;
413
414 if (strcmp(path, "/cgroup") == 0) {
415 // get list of controllers
416 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
417 int i;
418
419 if (!list)
420 return -EIO;
421 /* TODO - collect the list of controllers at fuse_init */
422 for (i = 0; list[i]; i++) {
423 if (filler(buf, list[i], NULL, 0) != 0) {
424 return -EIO;
425 }
426 }
427 return 0;
428 }
429
430 // return list of keys for the controller, and list of child cgroups
431 nih_local struct cgm_keys **list = NULL;
432 const char *cgroup;
433 nih_local char *controller = NULL;
434 int i;
435
436 controller = pick_controller_from_path(fc, path);
437 if (!controller)
438 return -EIO;
439
440 cgroup = find_cgroup_in_path(path);
441 if (!cgroup) {
442 /* this is just /cgroup/controller, return its contents */
443 cgroup = "/";
444 }
445
446 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
447 return -EPERM;
448
449 if (!cgm_list_keys(controller, cgroup, &list))
450 return -EINVAL;
451 for (i = 0; list[i]; i++) {
452 if (filler(buf, list[i]->name, NULL, 0) != 0) {
453 return -EIO;
454 }
455 }
456
457 // now get the list of child cgroups
458 nih_local char **clist;
459
460 if (!cgm_list_children(controller, cgroup, &clist))
461 return 0;
462 for (i = 0; clist[i]; i++) {
463 if (filler(buf, clist[i], NULL, 0) != 0) {
464 return -EIO;
465 }
466 }
467 return 0;
468 }
469
470 static int cg_releasedir(const char *path, struct fuse_file_info *fi)
471 {
472 return 0;
473 }
474
475 static int cg_open(const char *path, struct fuse_file_info *fi)
476 {
477 nih_local char *controller = NULL;
478 const char *cgroup;
479 char *fpath = NULL, *path1, *path2;
480 nih_local char * cgdir = NULL;
481 nih_local struct cgm_keys *k = NULL;
482 struct fuse_context *fc = fuse_get_context();
483
484 if (!fc)
485 return -EIO;
486
487 controller = pick_controller_from_path(fc, path);
488 if (!controller)
489 return -EIO;
490 cgroup = find_cgroup_in_path(path);
491 if (!cgroup)
492 return -EINVAL;
493
494 get_cgdir_and_path(cgroup, &cgdir, &fpath);
495 if (!fpath) {
496 path1 = "/";
497 path2 = cgdir;
498 } else {
499 path1 = cgdir;
500 path2 = fpath;
501 }
502
503 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
504 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
505 return -EPERM;
506
507 /* TODO - we want to cache this info for read/write */
508 return 0;
509 }
510
511 return -EINVAL;
512 }
513
514 static int cg_read(const char *path, char *buf, size_t size, off_t offset,
515 struct fuse_file_info *fi)
516 {
517 nih_local char *controller = NULL;
518 const char *cgroup;
519 char *fpath = NULL, *path1, *path2;
520 struct fuse_context *fc = fuse_get_context();
521 nih_local char * cgdir = NULL;
522 nih_local struct cgm_keys *k = NULL;
523
524 if (offset)
525 return -EIO;
526
527 if (!fc)
528 return -EIO;
529
530 controller = pick_controller_from_path(fc, path);
531 if (!controller)
532 return -EIO;
533 cgroup = find_cgroup_in_path(path);
534 if (!cgroup)
535 return -EINVAL;
536
537 get_cgdir_and_path(cgroup, &cgdir, &fpath);
538 if (!fpath) {
539 path1 = "/";
540 path2 = cgdir;
541 } else {
542 path1 = cgdir;
543 path2 = fpath;
544 }
545
546 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
547 nih_local char *data = NULL;
548 int s;
549
550 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
551 return -EPERM;
552
553 if (!cgm_get_value(controller, path1, path2, &data))
554 return -EINVAL;
555
556 s = strlen(data);
557 if (s > size)
558 s = size;
559 memcpy(buf, data, s);
560
561 return s;
562 }
563
564 return -EINVAL;
565 }
566
567 int cg_write(const char *path, const char *buf, size_t size, off_t offset,
568 struct fuse_file_info *fi)
569 {
570 nih_local char *controller = NULL;
571 const char *cgroup;
572 char *fpath = NULL, *path1, *path2;
573 struct fuse_context *fc = fuse_get_context();
574 nih_local char * cgdir = NULL;
575 nih_local struct cgm_keys *k = NULL;
576
577 fprintf(stderr, "cg_write: starting\n");
578
579 if (offset)
580 return -EIO;
581
582 if (!fc)
583 return -EIO;
584
585 controller = pick_controller_from_path(fc, path);
586 if (!controller)
587 return -EIO;
588 cgroup = find_cgroup_in_path(path);
589 if (!cgroup)
590 return -EINVAL;
591
592 get_cgdir_and_path(cgroup, &cgdir, &fpath);
593 if (!fpath) {
594 path1 = "/";
595 path2 = cgdir;
596 } else {
597 path1 = cgdir;
598 path2 = fpath;
599 }
600
601 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
602 if (!fc_may_access(fc, controller, path1, path2, O_WRONLY))
603 return -EPERM;
604
605 if (!cgm_set_value(controller, path1, path2, buf))
606 return -EINVAL;
607
608 return size;
609 }
610
611 return -EINVAL;
612 }
613
614
615 int cg_mkdir(const char *path, mode_t mode)
616 {
617 struct fuse_context *fc = fuse_get_context();
618 nih_local struct cgm_keys **list = NULL;
619 char *fpath = NULL, *path1;
620 nih_local char * cgdir = NULL;
621 const char *cgroup;
622 nih_local char *controller = NULL;
623
624 if (!fc)
625 return -EIO;
626
627
628 controller = pick_controller_from_path(fc, path);
629 if (!controller)
630 return -EIO;
631
632 cgroup = find_cgroup_in_path(path);
633 if (!cgroup)
634 return -EIO;
635
636 get_cgdir_and_path(cgroup, &cgdir, &fpath);
637 if (!fpath)
638 path1 = "/";
639 else
640 path1 = cgdir;
641
642 if (!fc_may_access(fc, controller, path1, NULL, O_RDWR))
643 return -EPERM;
644
645
646 if (!cgm_create(controller, cgroup, fc->uid, fc->gid))
647 return -EINVAL;
648
649 return 0;
650 }
651
652 /*
653 * FUSE ops for /proc
654 */
655
656 static int proc_getattr(const char *path, struct stat *sb)
657 {
658 if (strcmp(path, "/proc") != 0)
659 return -EINVAL;
660 sb->st_mode = S_IFDIR | 00755;
661 sb->st_nlink = 2;
662 return 0;
663 }
664
665 /*
666 * FUSE ops for /
667 * these just delegate to the /proc and /cgroup ops as
668 * needed
669 */
670
671 static int lxcfs_getattr(const char *path, struct stat *sb)
672 {
673 if (strcmp(path, "/") == 0) {
674 sb->st_mode = S_IFDIR | 00755;
675 sb->st_nlink = 2;
676 return 0;
677 }
678 if (strncmp(path, "/cgroup", 7) == 0) {
679 return cg_getattr(path, sb);
680 }
681 if (strncmp(path, "/proc", 7) == 0) {
682 return proc_getattr(path, sb);
683 }
684 return -EINVAL;
685 }
686
687 static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
688 {
689 if (strcmp(path, "/") == 0)
690 return 0;
691
692 if (strncmp(path, "/cgroup", 7) == 0) {
693 return cg_opendir(path, fi);
694 }
695 return -EINVAL;
696 }
697
698 static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
699 struct fuse_file_info *fi)
700 {
701 if (strcmp(path, "/") == 0) {
702 if (filler(buf, "proc", NULL, 0) != 0 ||
703 filler(buf, "cgroup", NULL, 0) != 0)
704 return -EINVAL;
705 return 0;
706 }
707 if (strncmp(path, "/cgroup", 7) == 0) {
708 return cg_readdir(path, buf, filler, offset, fi);
709 }
710 return -EINVAL;
711 }
712
713 static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
714 {
715 if (strcmp(path, "/") == 0)
716 return 0;
717 if (strncmp(path, "/cgroup", 7) == 0) {
718 return cg_releasedir(path, fi);
719 }
720 return -EINVAL;
721 }
722
723 static int lxcfs_open(const char *path, struct fuse_file_info *fi)
724 {
725 if (strncmp(path, "/cgroup", 7) == 0) {
726 return cg_open(path, fi);
727 }
728
729 return -EINVAL;
730 }
731
732 static int lxcfs_read(const char *path, char *buf, size_t size, off_t offset,
733 struct fuse_file_info *fi)
734 {
735 if (strncmp(path, "/cgroup", 7) == 0) {
736 return cg_read(path, buf, size, offset, fi);
737 }
738
739 return -EINVAL;
740 }
741
742 int lxcfs_write(const char *path, const char *buf, size_t size, off_t offset,
743 struct fuse_file_info *fi)
744 {
745 if (strncmp(path, "/cgroup", 7) == 0) {
746 return cg_write(path, buf, size, offset, fi);
747 }
748
749 return -EINVAL;
750 }
751
752 static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
753 {
754 return 0;
755 }
756
757 static int lxcfs_release(const char *path, struct fuse_file_info *fi)
758 {
759 return 0;
760 }
761
762 static int lxcfs_fsync(const char *path, int datasync, struct fuse_file_info *fi)
763 {
764 return 0;
765 }
766
767 int lxcfs_mkdir(const char *path, mode_t mode)
768 {
769 if (strncmp(path, "/cgroup", 7) == 0)
770 return cg_mkdir(path, mode);
771
772 return -EINVAL;
773 }
774
775 /*
776 * cat first does a truncate before doing ops->write. This doesn't
777 * really make sense for cgroups. So just return 0 always but do
778 * nothing.
779 */
780 int lxcfs_truncate(const char *path, off_t newsize)
781 {
782 if (strncmp(path, "/cgroup", 7) == 0)
783 return 0;
784 return -EINVAL;
785 }
786
787 const struct fuse_operations lxcfs_ops = {
788 .getattr = lxcfs_getattr,
789 .readlink = NULL,
790 .getdir = NULL,
791 .mknod = NULL,
792 .mkdir = lxcfs_mkdir,
793 .unlink = NULL,
794 .rmdir = NULL,
795 .symlink = NULL,
796 .rename = NULL,
797 .link = NULL,
798 .chmod = NULL,
799 .chown = NULL,
800 .truncate = lxcfs_truncate,
801 .utime = NULL,
802
803 .open = lxcfs_open,
804 .read = lxcfs_read,
805 .release = lxcfs_release,
806 .write = lxcfs_write,
807
808 .statfs = NULL,
809 .flush = lxcfs_flush,
810 .fsync = lxcfs_fsync,
811
812 .setxattr = NULL,
813 .getxattr = NULL,
814 .listxattr = NULL,
815 .removexattr = NULL,
816
817 .opendir = lxcfs_opendir,
818 .readdir = lxcfs_readdir,
819 .releasedir = lxcfs_releasedir,
820
821 .fsyncdir = NULL,
822 .init = NULL,
823 .destroy = NULL,
824 .access = NULL,
825 .create = NULL,
826 .ftruncate = NULL,
827 .fgetattr = NULL,
828 };
829
830 static void usage(const char *me)
831 {
832 fprintf(stderr, "Usage:\n");
833 fprintf(stderr, "\n");
834 fprintf(stderr, "%s [FUSE and mount options] mountpoint\n", me);
835 exit(1);
836 }
837
838 static bool is_help(char *w)
839 {
840 if (strcmp(w, "-h") == 0 ||
841 strcmp(w, "--help") == 0 ||
842 strcmp(w, "-help") == 0 ||
843 strcmp(w, "help") == 0)
844 return true;
845 return false;
846 }
847
848 int main(int argc, char *argv[])
849 {
850 int ret;
851 struct lxcfs_state *d;
852
853 if (argc < 2 || is_help(argv[1]))
854 usage(argv[0]);
855
856 d = malloc(sizeof(*d));
857 if (!d)
858 return -1;
859
860 if (!cgm_escape_cgroup())
861 fprintf(stderr, "WARNING: failed to escape to root cgroup\n");
862
863 if (!cgm_get_controllers(&d->subsystems))
864 return -1;
865
866 ret = fuse_main(argc, argv, &lxcfs_ops, d);
867
868 return ret;
869 }