]> git.proxmox.com Git - mirror_lxcfs.git/blob - lxcfs.c
remove stmt in lxcfs.c
[mirror_lxcfs.git] / lxcfs.c
1 /* lxcfs
2 *
3 * Copyright © 2014 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
5 *
6 * See COPYING file for details.
7 */
8
9 /*
10 * NOTES - make sure to run this as -s to avoid threading.
11 * TODO - can we enforce that here from the code?
12 */
13 #define FUSE_USE_VERSION 26
14
15 #include <stdio.h>
16 #include <dirent.h>
17 #include <fcntl.h>
18 #include <fuse.h>
19 #include <unistd.h>
20 #include <errno.h>
21 #include <stdbool.h>
22 #include <time.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <libgen.h>
26
27 #include <nih/alloc.h>
28 #include <nih/string.h>
29
30 #include "cgmanager.h"
31
32 struct lxcfs_state {
33 /*
34 * a null-terminated, nih-allocated list of the mounted subsystems. We
35 * detect this at startup.
36 */
37 char **subsystems;
38 };
39 #define LXCFS_DATA ((struct lxcfs_state *) fuse_get_context()->private_data)
40
41 /*
42 * Given a open file * to /proc/pid/{u,g}id_map, and an id
43 * valid in the caller's namespace, return the id mapped into
44 * pid's namespace.
45 * Returns the mapped id, or -1 on error.
46 */
47 unsigned int
48 convert_id_to_ns(FILE *idfile, unsigned int in_id)
49 {
50 unsigned int nsuid, // base id for a range in the idfile's namespace
51 hostuid, // base id for a range in the caller's namespace
52 count; // number of ids in this range
53 char line[400];
54 int ret;
55
56 fseek(idfile, 0L, SEEK_SET);
57 while (fgets(line, 400, idfile)) {
58 ret = sscanf(line, "%u %u %u\n", &nsuid, &hostuid, &count);
59 if (ret != 3)
60 continue;
61 if (hostuid + count < hostuid || nsuid + count < nsuid) {
62 /*
63 * uids wrapped around - unexpected as this is a procfile,
64 * so just bail.
65 */
66 fprintf(stderr, "pid wrapparound at entry %u %u %u in %s",
67 nsuid, hostuid, count, line);
68 return -1;
69 }
70 if (hostuid <= in_id && hostuid+count > in_id) {
71 /*
72 * now since hostuid <= in_id < hostuid+count, and
73 * hostuid+count and nsuid+count do not wrap around,
74 * we know that nsuid+(in_id-hostuid) which must be
75 * less that nsuid+(count) must not wrap around
76 */
77 return (in_id - hostuid) + nsuid;
78 }
79 }
80
81 // no answer found
82 return -1;
83 }
84
85 /*
86 * for is_privileged_over,
87 * specify whether we require the calling uid to be root in his
88 * namespace
89 */
90 #define NS_ROOT_REQD true
91 #define NS_ROOT_OPT false
92
93 static bool is_privileged_over(pid_t pid, uid_t uid, uid_t victim, bool req_ns_root)
94 {
95 nih_local char *fpath = NULL;
96 bool answer = false;
97 uid_t nsuid;
98
99 if (victim == -1 || uid == -1)
100 return false;
101
102 /*
103 * If the request is one not requiring root in the namespace,
104 * then having the same uid suffices. (i.e. uid 1000 has write
105 * access to files owned by uid 1000
106 */
107 if (!req_ns_root && uid == victim)
108 return true;
109
110 fpath = NIH_MUST( nih_sprintf(NULL, "/proc/%d/uid_map", pid) );
111 FILE *f = fopen(fpath, "r");
112 if (!f)
113 return false;
114
115 /* if caller's not root in his namespace, reject */
116 nsuid = convert_id_to_ns(f, uid);
117 if (nsuid)
118 goto out;
119
120 /*
121 * If victim is not mapped into caller's ns, reject.
122 * XXX I'm not sure this check is needed given that fuse
123 * will be sending requests where the vfs has converted
124 */
125 nsuid = convert_id_to_ns(f, victim);
126 if (nsuid == -1)
127 goto out;
128
129 answer = true;
130
131 out:
132 fclose(f);
133 return answer;
134 }
135
136 static bool perms_include(int fmode, mode_t req_mode)
137 {
138 mode_t r;
139
140 switch (req_mode & O_ACCMODE) {
141 case O_RDONLY:
142 r = S_IROTH;
143 break;
144 case O_WRONLY:
145 r = S_IWOTH;
146 break;
147 case O_RDWR:
148 r = S_IROTH | S_IWOTH;
149 break;
150 default:
151 return false;
152 }
153 return ((fmode & r) == r);
154 }
155
156 static char *get_next_cgroup_dir(const char *taskcg, const char *querycg)
157 {
158 char *start, *end;
159
160 if (strlen(taskcg) <= strlen(querycg)) {
161 fprintf(stderr, "%s: I was fed bad input\n", __func__);
162 return NULL;
163 }
164
165 if (strcmp(querycg, "/") == 0)
166 start = NIH_MUST( nih_strdup(NULL, taskcg + 1) );
167 else
168 start = NIH_MUST( nih_strdup(NULL, taskcg + strlen(querycg) + 1) );
169 end = strchr(start, '/');
170 if (end)
171 *end = '\0';
172 return start;
173 }
174
175 /*
176 * check whether a fuse context may access a cgroup dir or file
177 *
178 * If file is not null, it is a cgroup file to check under cg.
179 * If file is null, then we are checking perms on cg itself.
180 *
181 * For files we can check the mode of the list_keys result.
182 * For cgroups, we must make assumptions based on the files under the
183 * cgroup, because cgmanager doesn't tell us ownership/perms of cgroups
184 * yet.
185 */
186 static bool fc_may_access(struct fuse_context *fc, const char *contrl, const char *cg, const char *file, mode_t mode)
187 {
188 nih_local struct cgm_keys **list = NULL;
189 int i;
190
191 if (!file)
192 file = "tasks";
193
194 if (*file == '/')
195 file++;
196
197 if (!cgm_list_keys(contrl, cg, &list))
198 return false;
199 for (i = 0; list[i]; i++) {
200 if (strcmp(list[i]->name, file) == 0) {
201 struct cgm_keys *k = list[i];
202 if (is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_OPT)) {
203 if (perms_include(k->mode >> 6, mode))
204 return true;
205 }
206 if (fc->gid == k->gid) {
207 if (perms_include(k->mode >> 3, mode))
208 return true;
209 }
210 return perms_include(k->mode, mode);
211 }
212 }
213
214 return false;
215 }
216
217 static void stripnewline(char *x)
218 {
219 size_t l = strlen(x);
220 if (l && x[l-1] == '\n')
221 x[l-1] = '\0';
222 }
223
224 /*
225 * If caller is in /a/b/c/d, he may only act on things under cg=/a/b/c/d.
226 * If caller is in /a, he may act on /a/b, but not on /b.
227 * if the answer is false and nextcg is not NULL, then *nextcg will point
228 * to a nih_alloc'd string containing the next cgroup directory under cg
229 */
230 static bool caller_is_in_ancestor(pid_t pid, const char *contrl, const char *cg, char **nextcg)
231 {
232 nih_local char *fnam = NULL;
233 FILE *f;
234 bool answer = false;
235 char *line = NULL;
236 size_t len = 0;
237
238 fnam = NIH_MUST( nih_sprintf(NULL, "/proc/%d/cgroup", pid) );
239 if (!(f = fopen(fnam, "r")))
240 return false;
241
242 while (getline(&line, &len, f) != -1) {
243 char *c1, *c2, *linecmp;
244 if (!line[0])
245 continue;
246 c1 = strchr(line, ':');
247 if (!c1)
248 goto out;
249 c1++;
250 c2 = strchr(c1, ':');
251 if (!c2)
252 goto out;
253 *c2 = '\0';
254 if (strcmp(c1, contrl) != 0)
255 continue;
256 c2++;
257 stripnewline(c2);
258 /*
259 * callers pass in '/' for root cgroup, otherwise they pass
260 * in a cgroup without leading '/'
261 */
262 linecmp = *cg == '/' ? c2 : c2+1;
263 if (strncmp(linecmp, cg, strlen(linecmp)) != 0) {
264 if (nextcg)
265 *nextcg = get_next_cgroup_dir(linecmp, cg);
266 goto out;
267 }
268 answer = true;
269 goto out;
270 }
271
272 out:
273 fclose(f);
274 free(line);
275 return answer;
276 }
277
278 /*
279 * given /cgroup/freezer/a/b, return "freezer". this will be nih-allocated
280 * and needs to be nih_freed.
281 */
282 static char *pick_controller_from_path(struct fuse_context *fc, const char *path)
283 {
284 const char *p1;
285 char *ret, *slash;
286
287 if (strlen(path) < 9)
288 return NULL;
289 p1 = path+8;
290 ret = nih_strdup(NULL, p1);
291 if (!ret)
292 return ret;
293 slash = strstr(ret, "/");
294 if (slash)
295 *slash = '\0';
296
297 /* verify that it is a subsystem */
298 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
299 int i;
300 if (!list) {
301 nih_free(ret);
302 return NULL;
303 }
304 for (i = 0; list[i]; i++) {
305 if (strcmp(list[i], ret) == 0)
306 return ret;
307 }
308 nih_free(ret);
309 return NULL;
310 }
311
312 /*
313 * Find the start of cgroup in /cgroup/controller/the/cgroup/path
314 * Note that the returned value may include files (keynames) etc
315 */
316 static const char *find_cgroup_in_path(const char *path)
317 {
318 const char *p1;
319
320 if (strlen(path) < 9)
321 return NULL;
322 p1 = strstr(path+8, "/");
323 if (!p1)
324 return NULL;
325 return p1+1;
326 }
327
328 static bool is_child_cgroup(const char *contr, const char *dir, const char *f)
329 {
330 nih_local char **list = NULL;
331 int i;
332
333 if (!f)
334 return false;
335 if (*f == '/')
336 f++;
337
338 if (!cgm_list_children(contr, dir, &list))
339 return false;
340 for (i = 0; list[i]; i++) {
341 if (strcmp(list[i], f) == 0)
342 return true;
343 }
344
345 return false;
346 }
347
348 static struct cgm_keys *get_cgroup_key(const char *contr, const char *dir, const char *f)
349 {
350 nih_local struct cgm_keys **list = NULL;
351 struct cgm_keys *k;
352 int i;
353
354 if (!f)
355 return NULL;
356 if (*f == '/')
357 f++;
358 if (!cgm_list_keys(contr, dir, &list))
359 return NULL;
360 for (i = 0; list[i]; i++) {
361 if (strcmp(list[i]->name, f) == 0) {
362 k = NIH_MUST( nih_alloc(NULL, (sizeof(*k))) );
363 k->name = NIH_MUST( nih_strdup(k, list[i]->name) );
364 k->uid = list[i]->uid;
365 k->gid = list[i]->gid;
366 k->mode = list[i]->mode;
367 return k;
368 }
369 }
370
371 return NULL;
372 }
373
374 static void get_cgdir_and_path(const char *cg, char **dir, char **file)
375 {
376 char *p;
377
378 *dir = NIH_MUST( nih_strdup(NULL, cg) );
379 *file = strrchr(cg, '/');
380 if (!*file) {
381 *file = NULL;
382 return;
383 }
384 p = strrchr(*dir, '/');
385 *p = '\0';
386 }
387
388 static size_t get_file_size(const char *contrl, const char *cg, const char *f)
389 {
390 nih_local char *data = NULL;
391 size_t s;
392 if (!cgm_get_value(contrl, cg, f, &data))
393 return -EINVAL;
394 s = strlen(data);
395 return s;
396 }
397
398 /*
399 * FUSE ops for /cgroup
400 */
401
402 static int cg_getattr(const char *path, struct stat *sb)
403 {
404 struct timespec now;
405 struct fuse_context *fc = fuse_get_context();
406 nih_local char * cgdir = NULL;
407 char *fpath = NULL, *path1, *path2;
408 nih_local struct cgm_keys *k = NULL;
409 const char *cgroup;
410 nih_local char *controller = NULL;
411
412
413 if (!fc)
414 return -EIO;
415
416 memset(sb, 0, sizeof(struct stat));
417
418 if (clock_gettime(CLOCK_REALTIME, &now) < 0)
419 return -EINVAL;
420
421 sb->st_uid = sb->st_gid = 0;
422 sb->st_atim = sb->st_mtim = sb->st_ctim = now;
423 sb->st_size = 0;
424
425 if (strcmp(path, "/cgroup") == 0) {
426 sb->st_mode = S_IFDIR | 00755;
427 sb->st_nlink = 2;
428 return 0;
429 }
430
431 controller = pick_controller_from_path(fc, path);
432 if (!controller)
433 return -EIO;
434 cgroup = find_cgroup_in_path(path);
435 if (!cgroup) {
436 empty:
437 /* this is just /cgroup/controller, return it as a dir */
438 sb->st_mode = S_IFDIR | 00755;
439 sb->st_nlink = 2;
440 return 0;
441 }
442
443 get_cgdir_and_path(cgroup, &cgdir, &fpath);
444
445 if (!fpath) {
446 path1 = "/";
447 path2 = cgdir;
448 } else {
449 path1 = cgdir;
450 path2 = fpath;
451 }
452
453 /* check that cgcopy is either a child cgroup of cgdir, or listed in its keys.
454 * Then check that caller's cgroup is under path if fpath is a child
455 * cgroup, or cgdir if fpath is a file */
456
457 if (is_child_cgroup(controller, path1, path2)) {
458 if (!caller_is_in_ancestor(fc->pid, controller, cgroup, NULL))
459 goto empty;
460 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
461 return -EPERM;
462
463 // get uid, gid, from '/tasks' file and make up a mode
464 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
465 sb->st_mode = S_IFDIR | 00755;
466 k = get_cgroup_key(controller, cgroup, "tasks");
467 if (!k) {
468 sb->st_uid = sb->st_gid = 0;
469 } else {
470 sb->st_uid = k->uid;
471 sb->st_gid = k->gid;
472 }
473 sb->st_nlink = 2;
474 return 0;
475 }
476
477 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
478 if (!caller_is_in_ancestor(fc->pid, controller, path1, NULL))
479 return -ENOENT;
480 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
481 return -EPERM;
482
483 sb->st_mode = S_IFREG | k->mode;
484 sb->st_nlink = 1;
485 sb->st_uid = k->uid;
486 sb->st_gid = k->gid;
487 sb->st_size = get_file_size(controller, path1, path2);
488 return 0;
489 }
490
491 return -ENOENT;
492 }
493
494 static int cg_opendir(const char *path, struct fuse_file_info *fi)
495 {
496 return 0;
497 }
498
499 static int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
500 struct fuse_file_info *fi)
501 {
502 struct fuse_context *fc = fuse_get_context();
503
504 if (!fc)
505 return -EIO;
506
507 if (strcmp(path, "/cgroup") == 0) {
508 // get list of controllers
509 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
510 int i;
511
512 if (!list)
513 return -EIO;
514 /* TODO - collect the list of controllers at fuse_init */
515 for (i = 0; list[i]; i++) {
516 if (filler(buf, list[i], NULL, 0) != 0) {
517 return -EIO;
518 }
519 }
520 return 0;
521 }
522
523 // return list of keys for the controller, and list of child cgroups
524 nih_local struct cgm_keys **list = NULL;
525 const char *cgroup;
526 nih_local char *controller = NULL;
527 int i;
528 nih_local char *nextcg = NULL;
529
530 controller = pick_controller_from_path(fc, path);
531 if (!controller)
532 return -EIO;
533
534 cgroup = find_cgroup_in_path(path);
535 if (!cgroup) {
536 /* this is just /cgroup/controller, return its contents */
537 cgroup = "/";
538 }
539
540 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
541 return -EPERM;
542
543 if (!cgm_list_keys(controller, cgroup, &list))
544 // not a valid cgroup
545 return -EINVAL;
546
547 if (!caller_is_in_ancestor(fc->pid, controller, cgroup, &nextcg)) {
548 if (nextcg) {
549 int ret;
550 ret = filler(buf, nextcg, NULL, 0);
551 if (ret != 0)
552 return -EIO;
553 }
554 return 0;
555 }
556
557 for (i = 0; list[i]; i++) {
558 if (filler(buf, list[i]->name, NULL, 0) != 0) {
559 return -EIO;
560 }
561 }
562
563 // now get the list of child cgroups
564 nih_local char **clist;
565
566 if (!cgm_list_children(controller, cgroup, &clist))
567 return 0;
568 for (i = 0; clist[i]; i++) {
569 if (filler(buf, clist[i], NULL, 0) != 0) {
570 return -EIO;
571 }
572 }
573 return 0;
574 }
575
576 static int cg_releasedir(const char *path, struct fuse_file_info *fi)
577 {
578 return 0;
579 }
580
581 static int cg_open(const char *path, struct fuse_file_info *fi)
582 {
583 nih_local char *controller = NULL;
584 const char *cgroup;
585 char *fpath = NULL, *path1, *path2;
586 nih_local char * cgdir = NULL;
587 nih_local struct cgm_keys *k = NULL;
588 struct fuse_context *fc = fuse_get_context();
589
590 if (!fc)
591 return -EIO;
592
593 controller = pick_controller_from_path(fc, path);
594 if (!controller)
595 return -EIO;
596 cgroup = find_cgroup_in_path(path);
597 if (!cgroup)
598 return -EINVAL;
599
600 get_cgdir_and_path(cgroup, &cgdir, &fpath);
601 if (!fpath) {
602 path1 = "/";
603 path2 = cgdir;
604 } else {
605 path1 = cgdir;
606 path2 = fpath;
607 }
608
609 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
610 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
611 return -EPERM;
612
613 /* TODO - we want to cache this info for read/write */
614 return 0;
615 }
616
617 return -EINVAL;
618 }
619
620 static int cg_read(const char *path, char *buf, size_t size, off_t offset,
621 struct fuse_file_info *fi)
622 {
623 nih_local char *controller = NULL;
624 const char *cgroup;
625 char *fpath = NULL, *path1, *path2;
626 struct fuse_context *fc = fuse_get_context();
627 nih_local char * cgdir = NULL;
628 nih_local struct cgm_keys *k = NULL;
629
630 if (offset)
631 return -EIO;
632
633 if (!fc)
634 return -EIO;
635
636 controller = pick_controller_from_path(fc, path);
637 if (!controller)
638 return -EIO;
639 cgroup = find_cgroup_in_path(path);
640 if (!cgroup)
641 return -EINVAL;
642
643 get_cgdir_and_path(cgroup, &cgdir, &fpath);
644 if (!fpath) {
645 path1 = "/";
646 path2 = cgdir;
647 } else {
648 path1 = cgdir;
649 path2 = fpath;
650 }
651
652 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
653 nih_local char *data = NULL;
654 int s;
655
656 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
657 return -EPERM;
658
659 if (!cgm_get_value(controller, path1, path2, &data))
660 return -EINVAL;
661
662 s = strlen(data);
663 if (s > size)
664 s = size;
665 memcpy(buf, data, s);
666
667 return s;
668 }
669
670 return -EINVAL;
671 }
672
673 int cg_write(const char *path, const char *buf, size_t size, off_t offset,
674 struct fuse_file_info *fi)
675 {
676 nih_local char *controller = NULL;
677 const char *cgroup;
678 char *fpath = NULL, *path1, *path2;
679 struct fuse_context *fc = fuse_get_context();
680 nih_local char * cgdir = NULL;
681 nih_local struct cgm_keys *k = NULL;
682
683 if (offset)
684 return -EIO;
685
686 if (!fc)
687 return -EIO;
688
689 controller = pick_controller_from_path(fc, path);
690 if (!controller)
691 return -EIO;
692 cgroup = find_cgroup_in_path(path);
693 if (!cgroup)
694 return -EINVAL;
695
696 get_cgdir_and_path(cgroup, &cgdir, &fpath);
697 if (!fpath) {
698 path1 = "/";
699 path2 = cgdir;
700 } else {
701 path1 = cgdir;
702 path2 = fpath;
703 }
704
705 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
706 if (!fc_may_access(fc, controller, path1, path2, O_WRONLY))
707 return -EPERM;
708
709 if (!cgm_set_value(controller, path1, path2, buf))
710 return -EINVAL;
711
712 return size;
713 }
714
715 return -EINVAL;
716 }
717
718 int cg_chown(const char *path, uid_t uid, gid_t gid)
719 {
720 struct fuse_context *fc = fuse_get_context();
721 nih_local char * cgdir = NULL;
722 char *fpath = NULL, *path1, *path2;
723 nih_local struct cgm_keys *k = NULL;
724 const char *cgroup;
725 nih_local char *controller = NULL;
726
727
728 if (!fc)
729 return -EIO;
730
731 if (strcmp(path, "/cgroup") == 0)
732 return -EINVAL;
733
734 controller = pick_controller_from_path(fc, path);
735 if (!controller)
736 return -EIO;
737 cgroup = find_cgroup_in_path(path);
738 if (!cgroup)
739 /* this is just /cgroup/controller */
740 return -EINVAL;
741
742 get_cgdir_and_path(cgroup, &cgdir, &fpath);
743
744 if (!fpath) {
745 path1 = "/";
746 path2 = cgdir;
747 } else {
748 path1 = cgdir;
749 path2 = fpath;
750 }
751
752 if (is_child_cgroup(controller, path1, path2)) {
753 // get uid, gid, from '/tasks' file and make up a mode
754 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
755 k = get_cgroup_key(controller, cgroup, "tasks");
756
757 } else
758 k = get_cgroup_key(controller, path1, path2);
759
760 if (!k)
761 return -EINVAL;
762
763 /*
764 * This being a fuse request, the uid and gid must be valid
765 * in the caller's namespace. So we can just check to make
766 * sure that the caller is root in his uid, and privileged
767 * over the file's current owner.
768 */
769 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_REQD))
770 return -EPERM;
771
772 if (!cgm_chown_file(controller, cgroup, uid, gid))
773 return -EINVAL;
774 return 0;
775 }
776
777 int cg_chmod(const char *path, mode_t mode)
778 {
779 struct fuse_context *fc = fuse_get_context();
780 nih_local char * cgdir = NULL;
781 char *fpath = NULL, *path1, *path2;
782 nih_local struct cgm_keys *k = NULL;
783 const char *cgroup;
784 nih_local char *controller = NULL;
785
786 if (!fc)
787 return -EIO;
788
789 if (strcmp(path, "/cgroup") == 0)
790 return -EINVAL;
791
792 controller = pick_controller_from_path(fc, path);
793 if (!controller)
794 return -EIO;
795 cgroup = find_cgroup_in_path(path);
796 if (!cgroup)
797 /* this is just /cgroup/controller */
798 return -EINVAL;
799
800 get_cgdir_and_path(cgroup, &cgdir, &fpath);
801
802 if (!fpath) {
803 path1 = "/";
804 path2 = cgdir;
805 } else {
806 path1 = cgdir;
807 path2 = fpath;
808 }
809
810 if (is_child_cgroup(controller, path1, path2)) {
811 // get uid, gid, from '/tasks' file and make up a mode
812 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
813 k = get_cgroup_key(controller, cgroup, "tasks");
814
815 } else
816 k = get_cgroup_key(controller, path1, path2);
817
818 if (!k)
819 return -EINVAL;
820
821 /*
822 * This being a fuse request, the uid and gid must be valid
823 * in the caller's namespace. So we can just check to make
824 * sure that the caller is root in his uid, and privileged
825 * over the file's current owner.
826 */
827 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_OPT))
828 return -EPERM;
829
830 if (!cgm_chmod_file(controller, cgroup, mode))
831 return -EINVAL;
832 return 0;
833 }
834
835 int cg_mkdir(const char *path, mode_t mode)
836 {
837 struct fuse_context *fc = fuse_get_context();
838 nih_local struct cgm_keys **list = NULL;
839 char *fpath = NULL, *path1;
840 nih_local char * cgdir = NULL;
841 const char *cgroup;
842 nih_local char *controller = NULL;
843
844 if (!fc)
845 return -EIO;
846
847
848 controller = pick_controller_from_path(fc, path);
849 if (!controller)
850 return -EIO;
851
852 cgroup = find_cgroup_in_path(path);
853 if (!cgroup)
854 return -EIO;
855
856 get_cgdir_and_path(cgroup, &cgdir, &fpath);
857 if (!fpath)
858 path1 = "/";
859 else
860 path1 = cgdir;
861
862 if (!fc_may_access(fc, controller, path1, NULL, O_RDWR))
863 return -EPERM;
864
865
866 if (!cgm_create(controller, cgroup, fc->uid, fc->gid))
867 return -EINVAL;
868
869 return 0;
870 }
871
872 static int cg_rmdir(const char *path)
873 {
874 struct fuse_context *fc = fuse_get_context();
875 nih_local struct cgm_keys **list = NULL;
876 char *fpath = NULL;
877 nih_local char * cgdir = NULL;
878 const char *cgroup;
879 nih_local char *controller = NULL;
880
881 if (!fc)
882 return -EIO;
883
884
885 controller = pick_controller_from_path(fc, path);
886 if (!controller)
887 return -EIO;
888
889 cgroup = find_cgroup_in_path(path);
890 if (!cgroup)
891 return -EIO;
892
893 get_cgdir_and_path(cgroup, &cgdir, &fpath);
894 if (!fpath)
895 return -EINVAL;
896
897 if (!fc_may_access(fc, controller, cgdir, NULL, O_WRONLY))
898 return -EPERM;
899
900 if (!cgm_remove(controller, cgroup))
901 return -EINVAL;
902
903 return 0;
904 }
905
906 /*
907 * FUSE ops for /proc
908 */
909
910 static int proc_getattr(const char *path, struct stat *sb)
911 {
912 if (strcmp(path, "/proc") != 0)
913 return -EINVAL;
914 sb->st_mode = S_IFDIR | 00755;
915 sb->st_nlink = 2;
916 return 0;
917 }
918
919 /*
920 * FUSE ops for /
921 * these just delegate to the /proc and /cgroup ops as
922 * needed
923 */
924
925 static int lxcfs_getattr(const char *path, struct stat *sb)
926 {
927 if (strcmp(path, "/") == 0) {
928 sb->st_mode = S_IFDIR | 00755;
929 sb->st_nlink = 2;
930 return 0;
931 }
932 if (strncmp(path, "/cgroup", 7) == 0) {
933 return cg_getattr(path, sb);
934 }
935 if (strncmp(path, "/proc", 7) == 0) {
936 return proc_getattr(path, sb);
937 }
938 return -EINVAL;
939 }
940
941 static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
942 {
943 if (strcmp(path, "/") == 0)
944 return 0;
945
946 if (strncmp(path, "/cgroup", 7) == 0) {
947 return cg_opendir(path, fi);
948 }
949 return -EINVAL;
950 }
951
952 static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
953 struct fuse_file_info *fi)
954 {
955 if (strcmp(path, "/") == 0) {
956 if (filler(buf, "proc", NULL, 0) != 0 ||
957 filler(buf, "cgroup", NULL, 0) != 0)
958 return -EINVAL;
959 return 0;
960 }
961 if (strncmp(path, "/cgroup", 7) == 0) {
962 return cg_readdir(path, buf, filler, offset, fi);
963 }
964 return -EINVAL;
965 }
966
967 static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
968 {
969 if (strcmp(path, "/") == 0)
970 return 0;
971 if (strncmp(path, "/cgroup", 7) == 0) {
972 return cg_releasedir(path, fi);
973 }
974 return -EINVAL;
975 }
976
977 static int lxcfs_open(const char *path, struct fuse_file_info *fi)
978 {
979 if (strncmp(path, "/cgroup", 7) == 0) {
980 return cg_open(path, fi);
981 }
982
983 return -EINVAL;
984 }
985
986 static int lxcfs_read(const char *path, char *buf, size_t size, off_t offset,
987 struct fuse_file_info *fi)
988 {
989 if (strncmp(path, "/cgroup", 7) == 0) {
990 return cg_read(path, buf, size, offset, fi);
991 }
992
993 return -EINVAL;
994 }
995
996 int lxcfs_write(const char *path, const char *buf, size_t size, off_t offset,
997 struct fuse_file_info *fi)
998 {
999 if (strncmp(path, "/cgroup", 7) == 0) {
1000 return cg_write(path, buf, size, offset, fi);
1001 }
1002
1003 return -EINVAL;
1004 }
1005
1006 static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
1007 {
1008 return 0;
1009 }
1010
1011 static int lxcfs_release(const char *path, struct fuse_file_info *fi)
1012 {
1013 return 0;
1014 }
1015
1016 static int lxcfs_fsync(const char *path, int datasync, struct fuse_file_info *fi)
1017 {
1018 return 0;
1019 }
1020
1021 int lxcfs_mkdir(const char *path, mode_t mode)
1022 {
1023 if (strncmp(path, "/cgroup", 7) == 0)
1024 return cg_mkdir(path, mode);
1025
1026 return -EINVAL;
1027 }
1028
1029 int lxcfs_chown(const char *path, uid_t uid, gid_t gid)
1030 {
1031 if (strncmp(path, "/cgroup", 7) == 0)
1032 return cg_chown(path, uid, gid);
1033
1034 return -EINVAL;
1035 }
1036
1037 /*
1038 * cat first does a truncate before doing ops->write. This doesn't
1039 * really make sense for cgroups. So just return 0 always but do
1040 * nothing.
1041 */
1042 int lxcfs_truncate(const char *path, off_t newsize)
1043 {
1044 if (strncmp(path, "/cgroup", 7) == 0)
1045 return 0;
1046 return -EINVAL;
1047 }
1048
1049 int lxcfs_rmdir(const char *path)
1050 {
1051 if (strncmp(path, "/cgroup", 7) == 0)
1052 return cg_rmdir(path);
1053 return -EINVAL;
1054 }
1055
1056 int lxcfs_chmod(const char *path, mode_t mode)
1057 {
1058 if (strncmp(path, "/cgroup", 7) == 0)
1059 return cg_chmod(path, mode);
1060 return -EINVAL;
1061 }
1062
1063 const struct fuse_operations lxcfs_ops = {
1064 .getattr = lxcfs_getattr,
1065 .readlink = NULL,
1066 .getdir = NULL,
1067 .mknod = NULL,
1068 .mkdir = lxcfs_mkdir,
1069 .unlink = NULL,
1070 .rmdir = lxcfs_rmdir,
1071 .symlink = NULL,
1072 .rename = NULL,
1073 .link = NULL,
1074 .chmod = lxcfs_chmod,
1075 .chown = lxcfs_chown,
1076 .truncate = lxcfs_truncate,
1077 .utime = NULL,
1078
1079 .open = lxcfs_open,
1080 .read = lxcfs_read,
1081 .release = lxcfs_release,
1082 .write = lxcfs_write,
1083
1084 .statfs = NULL,
1085 .flush = lxcfs_flush,
1086 .fsync = lxcfs_fsync,
1087
1088 .setxattr = NULL,
1089 .getxattr = NULL,
1090 .listxattr = NULL,
1091 .removexattr = NULL,
1092
1093 .opendir = lxcfs_opendir,
1094 .readdir = lxcfs_readdir,
1095 .releasedir = lxcfs_releasedir,
1096
1097 .fsyncdir = NULL,
1098 .init = NULL,
1099 .destroy = NULL,
1100 .access = NULL,
1101 .create = NULL,
1102 .ftruncate = NULL,
1103 .fgetattr = NULL,
1104 };
1105
1106 static void usage(const char *me)
1107 {
1108 fprintf(stderr, "Usage:\n");
1109 fprintf(stderr, "\n");
1110 fprintf(stderr, "%s [FUSE and mount options] mountpoint\n", me);
1111 exit(1);
1112 }
1113
1114 static bool is_help(char *w)
1115 {
1116 if (strcmp(w, "-h") == 0 ||
1117 strcmp(w, "--help") == 0 ||
1118 strcmp(w, "-help") == 0 ||
1119 strcmp(w, "help") == 0)
1120 return true;
1121 return false;
1122 }
1123
1124 int main(int argc, char *argv[])
1125 {
1126 int ret;
1127 struct lxcfs_state *d;
1128
1129 if (argc < 2 || is_help(argv[1]))
1130 usage(argv[0]);
1131
1132 d = malloc(sizeof(*d));
1133 if (!d)
1134 return -1;
1135
1136 if (!cgm_escape_cgroup())
1137 fprintf(stderr, "WARNING: failed to escape to root cgroup\n");
1138
1139 if (!cgm_get_controllers(&d->subsystems))
1140 return -1;
1141
1142 ret = fuse_main(argc, argv, &lxcfs_ops, d);
1143
1144 return ret;
1145 }