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