]> git.proxmox.com Git - mirror_lxcfs.git/blob - lxcfs.c
implement cat of proc/cpuinfo
[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 /* this is just /cgroup/controller, return it as a dir */
437 sb->st_mode = S_IFDIR | 00755;
438 sb->st_nlink = 2;
439 return 0;
440 }
441
442 get_cgdir_and_path(cgroup, &cgdir, &fpath);
443
444 if (!fpath) {
445 path1 = "/";
446 path2 = cgdir;
447 } else {
448 path1 = cgdir;
449 path2 = fpath;
450 }
451
452 /* check that cgcopy is either a child cgroup of cgdir, or listed in its keys.
453 * Then check that caller's cgroup is under path if fpath is a child
454 * cgroup, or cgdir if fpath is a file */
455
456 if (is_child_cgroup(controller, path1, path2)) {
457 if (!caller_is_in_ancestor(fc->pid, controller, cgroup, NULL)) {
458 /* this is just /cgroup/controller, return it as a dir */
459 sb->st_mode = S_IFDIR | 00555;
460 sb->st_nlink = 2;
461 return 0;
462 }
463 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
464 return -EACCES;
465
466 // get uid, gid, from '/tasks' file and make up a mode
467 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
468 sb->st_mode = S_IFDIR | 00755;
469 k = get_cgroup_key(controller, cgroup, "tasks");
470 if (!k) {
471 sb->st_uid = sb->st_gid = 0;
472 } else {
473 sb->st_uid = k->uid;
474 sb->st_gid = k->gid;
475 }
476 sb->st_nlink = 2;
477 return 0;
478 }
479
480 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
481 if (!caller_is_in_ancestor(fc->pid, controller, path1, NULL))
482 return -ENOENT;
483 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
484 return -EACCES;
485
486 sb->st_mode = S_IFREG | k->mode;
487 sb->st_nlink = 1;
488 sb->st_uid = k->uid;
489 sb->st_gid = k->gid;
490 sb->st_size = get_file_size(controller, path1, path2);
491 return 0;
492 }
493
494 return -ENOENT;
495 }
496
497 /*
498 * TODO - cache these results in a table for use in opendir, free
499 * in releasedir
500 */
501 static int cg_opendir(const char *path, struct fuse_file_info *fi)
502 {
503 struct fuse_context *fc = fuse_get_context();
504 nih_local struct cgm_keys **list = NULL;
505 const char *cgroup;
506 nih_local char *controller = NULL;
507 nih_local char *nextcg = NULL;
508
509 if (!fc)
510 return -EIO;
511
512 if (strcmp(path, "/cgroup") == 0)
513 return 0;
514
515 // return list of keys for the controller, and list of child cgroups
516 controller = pick_controller_from_path(fc, path);
517 if (!controller)
518 return -EIO;
519
520 cgroup = find_cgroup_in_path(path);
521 if (!cgroup) {
522 /* this is just /cgroup/controller, return its contents */
523 cgroup = "/";
524 }
525
526 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
527 return -EACCES;
528 return 0;
529 }
530
531 static int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
532 struct fuse_file_info *fi)
533 {
534 struct fuse_context *fc = fuse_get_context();
535
536 if (!fc)
537 return -EIO;
538
539 if (strcmp(path, "/cgroup") == 0) {
540 // get list of controllers
541 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
542 int i;
543
544 if (!list)
545 return -EIO;
546
547 for (i = 0; list[i]; i++) {
548 if (filler(buf, list[i], NULL, 0) != 0) {
549 return -EIO;
550 }
551 }
552 return 0;
553 }
554
555 // return list of keys for the controller, and list of child cgroups
556 nih_local struct cgm_keys **list = NULL;
557 const char *cgroup;
558 nih_local char *controller = NULL;
559 int i;
560 nih_local char *nextcg = NULL;
561
562 controller = pick_controller_from_path(fc, path);
563 if (!controller)
564 return -EIO;
565
566 cgroup = find_cgroup_in_path(path);
567 if (!cgroup) {
568 /* this is just /cgroup/controller, return its contents */
569 cgroup = "/";
570 }
571
572 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
573 return -EACCES;
574
575 if (!cgm_list_keys(controller, cgroup, &list))
576 // not a valid cgroup
577 return -EINVAL;
578
579 if (!caller_is_in_ancestor(fc->pid, controller, cgroup, &nextcg)) {
580 if (nextcg) {
581 int ret;
582 ret = filler(buf, nextcg, NULL, 0);
583 if (ret != 0)
584 return -EIO;
585 }
586 return 0;
587 }
588
589 for (i = 0; list[i]; i++) {
590 if (filler(buf, list[i]->name, NULL, 0) != 0) {
591 return -EIO;
592 }
593 }
594
595 // now get the list of child cgroups
596 nih_local char **clist;
597
598 if (!cgm_list_children(controller, cgroup, &clist))
599 return 0;
600 for (i = 0; clist[i]; i++) {
601 if (filler(buf, clist[i], NULL, 0) != 0) {
602 return -EIO;
603 }
604 }
605 return 0;
606 }
607
608 static int cg_releasedir(const char *path, struct fuse_file_info *fi)
609 {
610 return 0;
611 }
612
613 static int cg_open(const char *path, struct fuse_file_info *fi)
614 {
615 nih_local char *controller = NULL;
616 const char *cgroup;
617 char *fpath = NULL, *path1, *path2;
618 nih_local char * cgdir = NULL;
619 nih_local struct cgm_keys *k = NULL;
620 struct fuse_context *fc = fuse_get_context();
621
622 if (!fc)
623 return -EIO;
624
625 controller = pick_controller_from_path(fc, path);
626 if (!controller)
627 return -EIO;
628 cgroup = find_cgroup_in_path(path);
629 if (!cgroup)
630 return -EINVAL;
631
632 get_cgdir_and_path(cgroup, &cgdir, &fpath);
633 if (!fpath) {
634 path1 = "/";
635 path2 = cgdir;
636 } else {
637 path1 = cgdir;
638 path2 = fpath;
639 }
640
641 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
642 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
643 // should never get here
644 return -EACCES;
645
646 /* TODO - we want to cache this info for read/write */
647 return 0;
648 }
649
650 return -EINVAL;
651 }
652
653 static int cg_read(const char *path, char *buf, size_t size, off_t offset,
654 struct fuse_file_info *fi)
655 {
656 nih_local char *controller = NULL;
657 const char *cgroup;
658 char *fpath = NULL, *path1, *path2;
659 struct fuse_context *fc = fuse_get_context();
660 nih_local char * cgdir = NULL;
661 nih_local struct cgm_keys *k = NULL;
662
663 if (offset)
664 return -EIO;
665
666 if (!fc)
667 return -EIO;
668
669 controller = pick_controller_from_path(fc, path);
670 if (!controller)
671 return -EINVAL;
672 cgroup = find_cgroup_in_path(path);
673 if (!cgroup)
674 return -EINVAL;
675
676 get_cgdir_and_path(cgroup, &cgdir, &fpath);
677 if (!fpath) {
678 path1 = "/";
679 path2 = cgdir;
680 } else {
681 path1 = cgdir;
682 path2 = fpath;
683 }
684
685 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
686 nih_local char *data = NULL;
687 int s;
688
689 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
690 // should never get here
691 return -EACCES;
692
693 if (!cgm_get_value(controller, path1, path2, &data))
694 return -EINVAL;
695
696 s = strlen(data);
697 if (s > size)
698 s = size;
699 memcpy(buf, data, s);
700
701 return s;
702 }
703
704 return -EINVAL;
705 }
706
707 int cg_write(const char *path, const char *buf, size_t size, off_t offset,
708 struct fuse_file_info *fi)
709 {
710 nih_local char *controller = NULL;
711 const char *cgroup;
712 char *fpath = NULL, *path1, *path2;
713 struct fuse_context *fc = fuse_get_context();
714 nih_local char * cgdir = NULL;
715 nih_local struct cgm_keys *k = NULL;
716
717 if (offset)
718 return -EINVAL;
719
720 if (!fc)
721 return -EIO;
722
723 controller = pick_controller_from_path(fc, path);
724 if (!controller)
725 return -EINVAL;
726 cgroup = find_cgroup_in_path(path);
727 if (!cgroup)
728 return -EINVAL;
729
730 get_cgdir_and_path(cgroup, &cgdir, &fpath);
731 if (!fpath) {
732 path1 = "/";
733 path2 = cgdir;
734 } else {
735 path1 = cgdir;
736 path2 = fpath;
737 }
738
739 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
740 if (!fc_may_access(fc, controller, path1, path2, O_WRONLY))
741 return -EACCES;
742
743 if (!cgm_set_value(controller, path1, path2, buf))
744 return -EINVAL;
745
746 return size;
747 }
748
749 return -EINVAL;
750 }
751
752 int cg_chown(const char *path, uid_t uid, gid_t gid)
753 {
754 struct fuse_context *fc = fuse_get_context();
755 nih_local char * cgdir = NULL;
756 char *fpath = NULL, *path1, *path2;
757 nih_local struct cgm_keys *k = NULL;
758 const char *cgroup;
759 nih_local char *controller = NULL;
760
761
762 if (!fc)
763 return -EIO;
764
765 if (strcmp(path, "/cgroup") == 0)
766 return -EINVAL;
767
768 controller = pick_controller_from_path(fc, path);
769 if (!controller)
770 return -EINVAL;
771 cgroup = find_cgroup_in_path(path);
772 if (!cgroup)
773 /* this is just /cgroup/controller */
774 return -EINVAL;
775
776 get_cgdir_and_path(cgroup, &cgdir, &fpath);
777
778 if (!fpath) {
779 path1 = "/";
780 path2 = cgdir;
781 } else {
782 path1 = cgdir;
783 path2 = fpath;
784 }
785
786 if (is_child_cgroup(controller, path1, path2)) {
787 // get uid, gid, from '/tasks' file and make up a mode
788 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
789 k = get_cgroup_key(controller, cgroup, "tasks");
790
791 } else
792 k = get_cgroup_key(controller, path1, path2);
793
794 if (!k)
795 return -EINVAL;
796
797 /*
798 * This being a fuse request, the uid and gid must be valid
799 * in the caller's namespace. So we can just check to make
800 * sure that the caller is root in his uid, and privileged
801 * over the file's current owner.
802 */
803 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_REQD))
804 return -EACCES;
805
806 if (!cgm_chown_file(controller, cgroup, uid, gid))
807 return -EINVAL;
808 return 0;
809 }
810
811 int cg_chmod(const char *path, mode_t mode)
812 {
813 struct fuse_context *fc = fuse_get_context();
814 nih_local char * cgdir = NULL;
815 char *fpath = NULL, *path1, *path2;
816 nih_local struct cgm_keys *k = NULL;
817 const char *cgroup;
818 nih_local char *controller = NULL;
819
820 if (!fc)
821 return -EIO;
822
823 if (strcmp(path, "/cgroup") == 0)
824 return -EINVAL;
825
826 controller = pick_controller_from_path(fc, path);
827 if (!controller)
828 return -EINVAL;
829 cgroup = find_cgroup_in_path(path);
830 if (!cgroup)
831 /* this is just /cgroup/controller */
832 return -EINVAL;
833
834 get_cgdir_and_path(cgroup, &cgdir, &fpath);
835
836 if (!fpath) {
837 path1 = "/";
838 path2 = cgdir;
839 } else {
840 path1 = cgdir;
841 path2 = fpath;
842 }
843
844 if (is_child_cgroup(controller, path1, path2)) {
845 // get uid, gid, from '/tasks' file and make up a mode
846 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
847 k = get_cgroup_key(controller, cgroup, "tasks");
848
849 } else
850 k = get_cgroup_key(controller, path1, path2);
851
852 if (!k)
853 return -EINVAL;
854
855 /*
856 * This being a fuse request, the uid and gid must be valid
857 * in the caller's namespace. So we can just check to make
858 * sure that the caller is root in his uid, and privileged
859 * over the file's current owner.
860 */
861 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_OPT))
862 return -EPERM;
863
864 if (!cgm_chmod_file(controller, cgroup, mode))
865 return -EINVAL;
866 return 0;
867 }
868
869 int cg_mkdir(const char *path, mode_t mode)
870 {
871 struct fuse_context *fc = fuse_get_context();
872 nih_local struct cgm_keys **list = NULL;
873 char *fpath = NULL, *path1;
874 nih_local char * cgdir = NULL;
875 const char *cgroup;
876 nih_local char *controller = NULL;
877
878 if (!fc)
879 return -EIO;
880
881
882 controller = pick_controller_from_path(fc, path);
883 if (!controller)
884 return -EINVAL;
885
886 cgroup = find_cgroup_in_path(path);
887 if (!cgroup)
888 return -EINVAL;
889
890 get_cgdir_and_path(cgroup, &cgdir, &fpath);
891 if (!fpath)
892 path1 = "/";
893 else
894 path1 = cgdir;
895
896 if (!fc_may_access(fc, controller, path1, NULL, O_RDWR))
897 return -EACCES;
898
899
900 if (!cgm_create(controller, cgroup, fc->uid, fc->gid))
901 return -EINVAL;
902
903 return 0;
904 }
905
906 static int cg_rmdir(const char *path)
907 {
908 struct fuse_context *fc = fuse_get_context();
909 nih_local struct cgm_keys **list = NULL;
910 char *fpath = NULL;
911 nih_local char * cgdir = NULL;
912 const char *cgroup;
913 nih_local char *controller = NULL;
914
915 if (!fc)
916 return -EIO;
917
918
919 controller = pick_controller_from_path(fc, path);
920 if (!controller)
921 return -EINVAL;
922
923 cgroup = find_cgroup_in_path(path);
924 if (!cgroup)
925 return -EINVAL;
926
927 get_cgdir_and_path(cgroup, &cgdir, &fpath);
928 if (!fpath)
929 return -EINVAL;
930
931 if (!fc_may_access(fc, controller, cgdir, NULL, O_WRONLY))
932 return -EACCES;
933
934 if (!cgm_remove(controller, cgroup))
935 return -EINVAL;
936
937 return 0;
938 }
939
940 /*
941 * FUSE ops for /proc
942 */
943
944 static int proc_meminfo_read(char *buf, size_t size, off_t offset,
945 struct fuse_file_info *fi)
946 {
947 return 0;
948 }
949
950 /*
951 * Read the cpuset.cpus for cg
952 * Return the answer in a nih_alloced string
953 */
954 static char *get_cpuset(const char *cg)
955 {
956 char *answer;
957
958 if (!cgm_get_value("cpuset", cg, "cpuset.cpus", &answer))
959 return NULL;
960 return answer;
961 }
962
963 /*
964 * Helper functions for cpuset_in-set
965 */
966 char *cpuset_nexttok(const char *c)
967 {
968 char *r = strchr(c+1, ',');
969 if (r)
970 return r+1;
971 return NULL;
972 }
973
974 int cpuset_getrange(const char *c, int *a, int *b)
975 {
976 int ret;
977
978 ret = sscanf(c, "%d-%d", a, b);
979 return ret;
980 }
981
982 /*
983 * cpusets are in format "1,2-3,4"
984 * iow, comma-delimited ranges
985 */
986 static bool cpuset_in_set(const char *line, const char *cpuset)
987 {
988 int cpu;
989 const char *c;
990
991 if (sscanf(line, "processor : %d", &cpu) != 1)
992 return false;
993 for (c = cpuset; c; c = cpuset_nexttok(c)) {
994 int a, b, ret;
995
996 ret = cpuset_getrange(c, &a, &b);
997 if (ret == 1 && cpu == a)
998 return true;
999 if (ret != 2) // bad cpuset!
1000 return false;
1001 if (cpu >= a && cpu <= b)
1002 return true;
1003 }
1004
1005 return false;
1006 }
1007
1008 /*
1009 * check whether this is a '^processor" line in /proc/cpuinfo
1010 */
1011 static bool is_processor_line(const char *line)
1012 {
1013 int cpu;
1014
1015 if (sscanf(line, "processor : %d", &cpu) == 1)
1016 return true;
1017 return false;
1018 }
1019
1020 static char *get_pid_cgroup(pid_t pid, const char *contrl)
1021 {
1022 nih_local char *fnam = NULL;
1023 FILE *f;
1024 char *answer = NULL;
1025 char *line = NULL;
1026 size_t len = 0;
1027
1028 fnam = NIH_MUST( nih_sprintf(NULL, "/proc/%d/cgroup", pid) );
1029 if (!(f = fopen(fnam, "r")))
1030 return false;
1031
1032 while (getline(&line, &len, f) != -1) {
1033 char *c1, *c2;
1034 if (!line[0])
1035 continue;
1036 c1 = strchr(line, ':');
1037 if (!c1)
1038 goto out;
1039 c1++;
1040 c2 = strchr(c1, ':');
1041 if (!c2)
1042 goto out;
1043 *c2 = '\0';
1044 if (strcmp(c1, contrl) != 0)
1045 continue;
1046 c2++;
1047 stripnewline(c2);
1048 answer = NIH_MUST( nih_strdup(NULL, c2) );
1049 goto out;
1050 }
1051
1052 out:
1053 fclose(f);
1054 free(line);
1055 return answer;
1056 }
1057
1058 static int proc_cpuinfo_read(char *buf, size_t size, off_t offset,
1059 struct fuse_file_info *fi)
1060 {
1061 struct fuse_context *fc = fuse_get_context();
1062 nih_local char *cg = get_pid_cgroup(fc->pid, "cpuset");
1063 nih_local char *cpuset = NULL;
1064 char *line = NULL;
1065 size_t linelen = 0, total_len = 0;
1066 bool am_printing = false;
1067 int curcpu = -1;
1068 FILE *f;
1069
1070 if (offset)
1071 return -EINVAL;
1072
1073 if (!cg)
1074 return 0;
1075
1076 cpuset = get_cpuset(cg);
1077 if (!cpuset)
1078 return 0;
1079
1080 f = fopen("/proc/cpuinfo", "r");
1081 if (!f)
1082 return 0;
1083
1084 while (getline(&line, &linelen, f) != -1) {
1085 size_t l;
1086 if (is_processor_line(line)) {
1087 am_printing = cpuset_in_set(line, cpuset);
1088 if (am_printing) {
1089 curcpu ++;
1090 l = snprintf(buf, size, "processor : %d\n", curcpu);
1091 buf += l;
1092 size -= l;
1093 total_len += l;
1094 }
1095 continue;
1096 }
1097 if (am_printing) {
1098 l = snprintf(buf, size, "%s", line);
1099 buf += l;
1100 size -= l;
1101 total_len += l;
1102 }
1103 }
1104
1105 return total_len;
1106 }
1107
1108 static int proc_stat_read(char *buf, size_t size, off_t offset,
1109 struct fuse_file_info *fi)
1110 {
1111 return 0;
1112 }
1113
1114 static int proc_uptime_read(char *buf, size_t size, off_t offset,
1115 struct fuse_file_info *fi)
1116 {
1117 return 0;
1118 }
1119
1120 static off_t get_procfile_size(const char *which)
1121 {
1122 FILE *f = fopen(which, "r");
1123 char *line = NULL;
1124 size_t len = 0;
1125 ssize_t sz, answer = 0;
1126 if (!f)
1127 return 0;
1128
1129 while ((sz = getline(&line, &len, f)) != -1)
1130 answer += sz;
1131 fclose (f);
1132
1133 return answer;
1134 }
1135
1136 static int proc_getattr(const char *path, struct stat *sb)
1137 {
1138 struct timespec now;
1139
1140 memset(sb, 0, sizeof(struct stat));
1141 if (clock_gettime(CLOCK_REALTIME, &now) < 0)
1142 return -EINVAL;
1143 sb->st_uid = sb->st_gid = 0;
1144 sb->st_atim = sb->st_mtim = sb->st_ctim = now;
1145 if (strcmp(path, "/proc") == 0) {
1146 sb->st_mode = S_IFDIR | 00555;
1147 sb->st_nlink = 2;
1148 return 0;
1149 }
1150 if (strcmp(path, "/proc/meminfo") == 0 ||
1151 strcmp(path, "/proc/cpuinfo") == 0 ||
1152 strcmp(path, "/proc/uptime") == 0 ||
1153 strcmp(path, "/proc/stat") == 0) {
1154
1155 sb->st_size = get_procfile_size(path);
1156 sb->st_mode = S_IFREG | 00444;
1157 sb->st_nlink = 1;
1158 return 0;
1159 }
1160
1161 return -ENOENT;
1162 }
1163
1164 static int proc_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
1165 struct fuse_file_info *fi)
1166 {
1167 if (filler(buf, "cpuinfo", NULL, 0) != 0 ||
1168 filler(buf, "meminfo", NULL, 0) != 0 ||
1169 filler(buf, "stat", NULL, 0) != 0 ||
1170 filler(buf, "uptime", NULL, 0) != 0)
1171 return -EINVAL;
1172 return 0;
1173 }
1174
1175 static int proc_open(const char *path, struct fuse_file_info *fi)
1176 {
1177 if (strcmp(path, "/proc/meminfo") == 0 ||
1178 strcmp(path, "/proc/cpuinfo") == 0 ||
1179 strcmp(path, "/proc/uptime") == 0 ||
1180 strcmp(path, "/proc/stat") == 0)
1181 return 0;
1182 return -ENOENT;
1183 }
1184
1185 static int proc_read(const char *path, char *buf, size_t size, off_t offset,
1186 struct fuse_file_info *fi)
1187 {
1188 if (strcmp(path, "/proc/meminfo") == 0)
1189 return proc_meminfo_read(buf, size, offset, fi);
1190 if (strcmp(path, "/proc/cpuinfo") == 0)
1191 return proc_cpuinfo_read(buf, size, offset, fi);
1192 if (strcmp(path, "/proc/uptime") == 0)
1193 return proc_uptime_read(buf, size, offset, fi);
1194 if (strcmp(path, "/proc/stat") == 0)
1195 return proc_stat_read(buf, size, offset, fi);
1196 return -EINVAL;
1197 }
1198
1199 /*
1200 * FUSE ops for /
1201 * these just delegate to the /proc and /cgroup ops as
1202 * needed
1203 */
1204
1205 static int lxcfs_getattr(const char *path, struct stat *sb)
1206 {
1207 if (strcmp(path, "/") == 0) {
1208 sb->st_mode = S_IFDIR | 00755;
1209 sb->st_nlink = 2;
1210 return 0;
1211 }
1212 if (strncmp(path, "/cgroup", 7) == 0) {
1213 return cg_getattr(path, sb);
1214 }
1215 if (strncmp(path, "/proc", 5) == 0) {
1216 return proc_getattr(path, sb);
1217 }
1218 return -EINVAL;
1219 }
1220
1221 static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
1222 {
1223 if (strcmp(path, "/") == 0)
1224 return 0;
1225
1226 if (strncmp(path, "/cgroup", 7) == 0) {
1227 return cg_opendir(path, fi);
1228 }
1229 if (strcmp(path, "/proc") == 0)
1230 return 0;
1231 return -ENOENT;
1232 }
1233
1234 static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
1235 struct fuse_file_info *fi)
1236 {
1237 if (strcmp(path, "/") == 0) {
1238 if (filler(buf, "proc", NULL, 0) != 0 ||
1239 filler(buf, "cgroup", NULL, 0) != 0)
1240 return -EINVAL;
1241 return 0;
1242 }
1243 if (strncmp(path, "/cgroup", 7) == 0)
1244 return cg_readdir(path, buf, filler, offset, fi);
1245 if (strcmp(path, "/proc") == 0)
1246 return proc_readdir(path, buf, filler, offset, fi);
1247 return -EINVAL;
1248 }
1249
1250 static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
1251 {
1252 if (strcmp(path, "/") == 0)
1253 return 0;
1254 if (strncmp(path, "/cgroup", 7) == 0) {
1255 return cg_releasedir(path, fi);
1256 }
1257 if (strcmp(path, "/proc") == 0)
1258 return 0;
1259 return -EINVAL;
1260 }
1261
1262 static int lxcfs_open(const char *path, struct fuse_file_info *fi)
1263 {
1264 if (strncmp(path, "/cgroup", 7) == 0)
1265 return cg_open(path, fi);
1266 if (strncmp(path, "/proc", 5) == 0)
1267 return proc_open(path, fi);
1268
1269 return -EINVAL;
1270 }
1271
1272 static int lxcfs_read(const char *path, char *buf, size_t size, off_t offset,
1273 struct fuse_file_info *fi)
1274 {
1275 if (strncmp(path, "/cgroup", 7) == 0)
1276 return cg_read(path, buf, size, offset, fi);
1277 if (strncmp(path, "/proc", 5) == 0)
1278 return proc_read(path, buf, size, offset, fi);
1279
1280 return -EINVAL;
1281 }
1282
1283 int lxcfs_write(const char *path, const char *buf, size_t size, off_t offset,
1284 struct fuse_file_info *fi)
1285 {
1286 if (strncmp(path, "/cgroup", 7) == 0) {
1287 return cg_write(path, buf, size, offset, fi);
1288 }
1289
1290 return -EINVAL;
1291 }
1292
1293 static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
1294 {
1295 return 0;
1296 }
1297
1298 static int lxcfs_release(const char *path, struct fuse_file_info *fi)
1299 {
1300 return 0;
1301 }
1302
1303 static int lxcfs_fsync(const char *path, int datasync, struct fuse_file_info *fi)
1304 {
1305 return 0;
1306 }
1307
1308 int lxcfs_mkdir(const char *path, mode_t mode)
1309 {
1310 if (strncmp(path, "/cgroup", 7) == 0)
1311 return cg_mkdir(path, mode);
1312
1313 return -EINVAL;
1314 }
1315
1316 int lxcfs_chown(const char *path, uid_t uid, gid_t gid)
1317 {
1318 if (strncmp(path, "/cgroup", 7) == 0)
1319 return cg_chown(path, uid, gid);
1320
1321 return -EINVAL;
1322 }
1323
1324 /*
1325 * cat first does a truncate before doing ops->write. This doesn't
1326 * really make sense for cgroups. So just return 0 always but do
1327 * nothing.
1328 */
1329 int lxcfs_truncate(const char *path, off_t newsize)
1330 {
1331 if (strncmp(path, "/cgroup", 7) == 0)
1332 return 0;
1333 return -EINVAL;
1334 }
1335
1336 int lxcfs_rmdir(const char *path)
1337 {
1338 if (strncmp(path, "/cgroup", 7) == 0)
1339 return cg_rmdir(path);
1340 return -EINVAL;
1341 }
1342
1343 int lxcfs_chmod(const char *path, mode_t mode)
1344 {
1345 if (strncmp(path, "/cgroup", 7) == 0)
1346 return cg_chmod(path, mode);
1347 return -EINVAL;
1348 }
1349
1350 const struct fuse_operations lxcfs_ops = {
1351 .getattr = lxcfs_getattr,
1352 .readlink = NULL,
1353 .getdir = NULL,
1354 .mknod = NULL,
1355 .mkdir = lxcfs_mkdir,
1356 .unlink = NULL,
1357 .rmdir = lxcfs_rmdir,
1358 .symlink = NULL,
1359 .rename = NULL,
1360 .link = NULL,
1361 .chmod = lxcfs_chmod,
1362 .chown = lxcfs_chown,
1363 .truncate = lxcfs_truncate,
1364 .utime = NULL,
1365
1366 .open = lxcfs_open,
1367 .read = lxcfs_read,
1368 .release = lxcfs_release,
1369 .write = lxcfs_write,
1370
1371 .statfs = NULL,
1372 .flush = lxcfs_flush,
1373 .fsync = lxcfs_fsync,
1374
1375 .setxattr = NULL,
1376 .getxattr = NULL,
1377 .listxattr = NULL,
1378 .removexattr = NULL,
1379
1380 .opendir = lxcfs_opendir,
1381 .readdir = lxcfs_readdir,
1382 .releasedir = lxcfs_releasedir,
1383
1384 .fsyncdir = NULL,
1385 .init = NULL,
1386 .destroy = NULL,
1387 .access = NULL,
1388 .create = NULL,
1389 .ftruncate = NULL,
1390 .fgetattr = NULL,
1391 };
1392
1393 static void usage(const char *me)
1394 {
1395 fprintf(stderr, "Usage:\n");
1396 fprintf(stderr, "\n");
1397 fprintf(stderr, "%s [FUSE and mount options] mountpoint\n", me);
1398 exit(1);
1399 }
1400
1401 static bool is_help(char *w)
1402 {
1403 if (strcmp(w, "-h") == 0 ||
1404 strcmp(w, "--help") == 0 ||
1405 strcmp(w, "-help") == 0 ||
1406 strcmp(w, "help") == 0)
1407 return true;
1408 return false;
1409 }
1410
1411 int main(int argc, char *argv[])
1412 {
1413 int ret;
1414 struct lxcfs_state *d;
1415
1416 if (argc < 2 || is_help(argv[1]))
1417 usage(argv[0]);
1418
1419 d = malloc(sizeof(*d));
1420 if (!d)
1421 return -1;
1422
1423 if (!cgm_escape_cgroup())
1424 fprintf(stderr, "WARNING: failed to escape to root cgroup\n");
1425
1426 if (!cgm_get_controllers(&d->subsystems))
1427 return -1;
1428
1429 ret = fuse_main(argc, argv, &lxcfs_ops, d);
1430
1431 return ret;
1432 }