]> git.proxmox.com Git - mirror_lxcfs.git/blame - lxcfs.c
check for permission at opendir
[mirror_lxcfs.git] / lxcfs.c
CommitLineData
758ad80c
SH
1/* lxcfs
2 *
3 * Copyright © 2014 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
5 *
f2799430 6 * See COPYING file for details.
758ad80c
SH
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
2183082c 15#include <stdio.h>
758ad80c
SH
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
32struct 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
053a659d
SH
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 */
47unsigned int
48convert_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
341b21ad
SH
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
93static bool is_privileged_over(pid_t pid, uid_t uid, uid_t victim, bool req_ns_root)
758ad80c 94{
053a659d
SH
95 nih_local char *fpath = NULL;
96 bool answer = false;
97 uid_t nsuid;
98
341b21ad
SH
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)
758ad80c
SH
108 return true;
109
053a659d
SH
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
341b21ad 115 /* if caller's not root in his namespace, reject */
053a659d
SH
116 nsuid = convert_id_to_ns(f, uid);
117 if (nsuid)
118 goto out;
119
341b21ad
SH
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 */
053a659d
SH
125 nsuid = convert_id_to_ns(f, victim);
126 if (nsuid == -1)
127 goto out;
128
129 answer = true;
130
131out:
132 fclose(f);
133 return answer;
758ad80c
SH
134}
135
136static bool perms_include(int fmode, mode_t req_mode)
137{
2ad6d2bd
SH
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);
758ad80c
SH
154}
155
3db25a35
SH
156static 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
758ad80c
SH
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 */
186static 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];
341b21ad 202 if (is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_OPT)) {
758ad80c
SH
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
3db25a35
SH
217static 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 */
230static 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
272out:
273 fclose(f);
274 free(line);
275 return answer;
276}
277
758ad80c
SH
278/*
279 * given /cgroup/freezer/a/b, return "freezer". this will be nih-allocated
280 * and needs to be nih_freed.
281 */
282static 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 */
316static 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
328static 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
348static 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
374static void get_cgdir_and_path(const char *cg, char **dir, char **file)
375{
758ad80c
SH
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
99978832
SH
388static 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}
2ad6d2bd 397
758ad80c 398/*
2ad6d2bd 399 * FUSE ops for /cgroup
758ad80c 400 */
2ad6d2bd 401
758ad80c
SH
402static 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;
758ad80c
SH
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 }
341b21ad 441
758ad80c
SH
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
758ad80c
SH
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)) {
f9a05025
SH
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 }
758ad80c 463 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
f9a05025 464 return -EACCES;
758ad80c 465
053a659d
SH
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) {
053a659d
SH
471 sb->st_uid = sb->st_gid = 0;
472 } else {
053a659d
SH
473 sb->st_uid = k->uid;
474 sb->st_gid = k->gid;
475 }
758ad80c
SH
476 sb->st_nlink = 2;
477 return 0;
478 }
479
480 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
3db25a35
SH
481 if (!caller_is_in_ancestor(fc->pid, controller, path1, NULL))
482 return -ENOENT;
758ad80c 483 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
f9a05025 484 return -EACCES;
758ad80c 485
758ad80c 486 sb->st_mode = S_IFREG | k->mode;
053a659d 487 sb->st_nlink = 1;
758ad80c
SH
488 sb->st_uid = k->uid;
489 sb->st_gid = k->gid;
99978832 490 sb->st_size = get_file_size(controller, path1, path2);
758ad80c
SH
491 return 0;
492 }
493
ab54b798 494 return -ENOENT;
758ad80c 495}
2183082c 496
7f163b71
SH
497/*
498 * TODO - cache these results in a table for use in opendir, free
499 * in releasedir
500 */
758ad80c 501static int cg_opendir(const char *path, struct fuse_file_info *fi)
2183082c 502{
7f163b71
SH
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 int i;
508 nih_local char *nextcg = NULL;
509
510 if (!fc)
511 return -EIO;
512
513 if (strcmp(path, "/cgroup") == 0)
514 return 0;
515
516 // return list of keys for the controller, and list of child cgroups
517 controller = pick_controller_from_path(fc, path);
518 if (!controller)
519 return -EIO;
520
521 cgroup = find_cgroup_in_path(path);
522 if (!cgroup) {
523 /* this is just /cgroup/controller, return its contents */
524 cgroup = "/";
525 }
526
527 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
528 return -EACCES;
758ad80c
SH
529 return 0;
530}
531
758ad80c
SH
532static int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
533 struct fuse_file_info *fi)
534{
535 struct fuse_context *fc = fuse_get_context();
536
537 if (!fc)
538 return -EIO;
539
540 if (strcmp(path, "/cgroup") == 0) {
541 // get list of controllers
542 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
543 int i;
544
545 if (!list)
546 return -EIO;
7f163b71 547
758ad80c
SH
548 for (i = 0; list[i]; i++) {
549 if (filler(buf, list[i], NULL, 0) != 0) {
550 return -EIO;
551 }
552 }
553 return 0;
554 }
555
556 // return list of keys for the controller, and list of child cgroups
557 nih_local struct cgm_keys **list = NULL;
558 const char *cgroup;
559 nih_local char *controller = NULL;
560 int i;
3db25a35 561 nih_local char *nextcg = NULL;
758ad80c
SH
562
563 controller = pick_controller_from_path(fc, path);
564 if (!controller)
565 return -EIO;
566
567 cgroup = find_cgroup_in_path(path);
568 if (!cgroup) {
569 /* this is just /cgroup/controller, return its contents */
570 cgroup = "/";
571 }
572
573 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
f9a05025 574 return -EACCES;
758ad80c
SH
575
576 if (!cgm_list_keys(controller, cgroup, &list))
3db25a35 577 // not a valid cgroup
758ad80c 578 return -EINVAL;
3db25a35
SH
579
580 if (!caller_is_in_ancestor(fc->pid, controller, cgroup, &nextcg)) {
581 if (nextcg) {
582 int ret;
583 ret = filler(buf, nextcg, NULL, 0);
584 if (ret != 0)
585 return -EIO;
586 }
587 return 0;
588 }
589
758ad80c 590 for (i = 0; list[i]; i++) {
758ad80c
SH
591 if (filler(buf, list[i]->name, NULL, 0) != 0) {
592 return -EIO;
593 }
594 }
595
596 // now get the list of child cgroups
597 nih_local char **clist;
598
599 if (!cgm_list_children(controller, cgroup, &clist))
600 return 0;
601 for (i = 0; clist[i]; i++) {
758ad80c
SH
602 if (filler(buf, clist[i], NULL, 0) != 0) {
603 return -EIO;
604 }
605 }
606 return 0;
607}
608
609static int cg_releasedir(const char *path, struct fuse_file_info *fi)
610{
611 return 0;
612}
613
99978832
SH
614static int cg_open(const char *path, struct fuse_file_info *fi)
615{
616 nih_local char *controller = NULL;
617 const char *cgroup;
618 char *fpath = NULL, *path1, *path2;
619 nih_local char * cgdir = NULL;
620 nih_local struct cgm_keys *k = NULL;
621 struct fuse_context *fc = fuse_get_context();
622
623 if (!fc)
624 return -EIO;
625
626 controller = pick_controller_from_path(fc, path);
627 if (!controller)
628 return -EIO;
629 cgroup = find_cgroup_in_path(path);
630 if (!cgroup)
631 return -EINVAL;
632
633 get_cgdir_and_path(cgroup, &cgdir, &fpath);
634 if (!fpath) {
635 path1 = "/";
636 path2 = cgdir;
637 } else {
638 path1 = cgdir;
639 path2 = fpath;
640 }
641
642 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
643 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
f9a05025
SH
644 // should never get here
645 return -EACCES;
99978832
SH
646
647 /* TODO - we want to cache this info for read/write */
648 return 0;
649 }
650
651 return -EINVAL;
652}
653
654static int cg_read(const char *path, char *buf, size_t size, off_t offset,
655 struct fuse_file_info *fi)
656{
657 nih_local char *controller = NULL;
658 const char *cgroup;
659 char *fpath = NULL, *path1, *path2;
660 struct fuse_context *fc = fuse_get_context();
661 nih_local char * cgdir = NULL;
662 nih_local struct cgm_keys *k = NULL;
663
664 if (offset)
665 return -EIO;
666
667 if (!fc)
668 return -EIO;
669
670 controller = pick_controller_from_path(fc, path);
671 if (!controller)
f9a05025 672 return -EINVAL;
99978832
SH
673 cgroup = find_cgroup_in_path(path);
674 if (!cgroup)
675 return -EINVAL;
676
677 get_cgdir_and_path(cgroup, &cgdir, &fpath);
678 if (!fpath) {
679 path1 = "/";
680 path2 = cgdir;
681 } else {
682 path1 = cgdir;
683 path2 = fpath;
684 }
685
686 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
687 nih_local char *data = NULL;
688 int s;
689
2ad6d2bd 690 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
f9a05025
SH
691 // should never get here
692 return -EACCES;
99978832
SH
693
694 if (!cgm_get_value(controller, path1, path2, &data))
695 return -EINVAL;
696
697 s = strlen(data);
698 if (s > size)
699 s = size;
700 memcpy(buf, data, s);
701
99978832
SH
702 return s;
703 }
704
705 return -EINVAL;
706}
707
2ad6d2bd
SH
708int cg_write(const char *path, const char *buf, size_t size, off_t offset,
709 struct fuse_file_info *fi)
710{
711 nih_local char *controller = NULL;
712 const char *cgroup;
713 char *fpath = NULL, *path1, *path2;
714 struct fuse_context *fc = fuse_get_context();
715 nih_local char * cgdir = NULL;
716 nih_local struct cgm_keys *k = NULL;
717
2ad6d2bd 718 if (offset)
f9a05025 719 return -EINVAL;
2ad6d2bd
SH
720
721 if (!fc)
722 return -EIO;
723
724 controller = pick_controller_from_path(fc, path);
725 if (!controller)
f9a05025 726 return -EINVAL;
2ad6d2bd
SH
727 cgroup = find_cgroup_in_path(path);
728 if (!cgroup)
729 return -EINVAL;
730
731 get_cgdir_and_path(cgroup, &cgdir, &fpath);
732 if (!fpath) {
733 path1 = "/";
734 path2 = cgdir;
735 } else {
736 path1 = cgdir;
737 path2 = fpath;
738 }
739
740 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
741 if (!fc_may_access(fc, controller, path1, path2, O_WRONLY))
f9a05025 742 return -EACCES;
2ad6d2bd
SH
743
744 if (!cgm_set_value(controller, path1, path2, buf))
745 return -EINVAL;
746
747 return size;
748 }
749
750 return -EINVAL;
751}
752
341b21ad
SH
753int cg_chown(const char *path, uid_t uid, gid_t gid)
754{
755 struct fuse_context *fc = fuse_get_context();
756 nih_local char * cgdir = NULL;
757 char *fpath = NULL, *path1, *path2;
758 nih_local struct cgm_keys *k = NULL;
759 const char *cgroup;
760 nih_local char *controller = NULL;
761
762
763 if (!fc)
764 return -EIO;
765
766 if (strcmp(path, "/cgroup") == 0)
767 return -EINVAL;
768
769 controller = pick_controller_from_path(fc, path);
770 if (!controller)
f9a05025 771 return -EINVAL;
341b21ad
SH
772 cgroup = find_cgroup_in_path(path);
773 if (!cgroup)
774 /* this is just /cgroup/controller */
775 return -EINVAL;
776
777 get_cgdir_and_path(cgroup, &cgdir, &fpath);
778
779 if (!fpath) {
780 path1 = "/";
781 path2 = cgdir;
782 } else {
783 path1 = cgdir;
784 path2 = fpath;
785 }
786
787 if (is_child_cgroup(controller, path1, path2)) {
788 // get uid, gid, from '/tasks' file and make up a mode
789 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
790 k = get_cgroup_key(controller, cgroup, "tasks");
791
792 } else
793 k = get_cgroup_key(controller, path1, path2);
794
795 if (!k)
796 return -EINVAL;
797
798 /*
799 * This being a fuse request, the uid and gid must be valid
800 * in the caller's namespace. So we can just check to make
801 * sure that the caller is root in his uid, and privileged
802 * over the file's current owner.
803 */
804 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_REQD))
f9a05025 805 return -EACCES;
341b21ad
SH
806
807 if (!cgm_chown_file(controller, cgroup, uid, gid))
808 return -EINVAL;
809 return 0;
810}
2ad6d2bd 811
fd2e4e03
SH
812int cg_chmod(const char *path, mode_t mode)
813{
0a1bb5ea
SH
814 struct fuse_context *fc = fuse_get_context();
815 nih_local char * cgdir = NULL;
816 char *fpath = NULL, *path1, *path2;
817 nih_local struct cgm_keys *k = NULL;
818 const char *cgroup;
819 nih_local char *controller = NULL;
820
821 if (!fc)
822 return -EIO;
823
824 if (strcmp(path, "/cgroup") == 0)
825 return -EINVAL;
826
827 controller = pick_controller_from_path(fc, path);
828 if (!controller)
f9a05025 829 return -EINVAL;
0a1bb5ea
SH
830 cgroup = find_cgroup_in_path(path);
831 if (!cgroup)
832 /* this is just /cgroup/controller */
833 return -EINVAL;
834
835 get_cgdir_and_path(cgroup, &cgdir, &fpath);
836
837 if (!fpath) {
838 path1 = "/";
839 path2 = cgdir;
840 } else {
841 path1 = cgdir;
842 path2 = fpath;
843 }
844
845 if (is_child_cgroup(controller, path1, path2)) {
846 // get uid, gid, from '/tasks' file and make up a mode
847 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
848 k = get_cgroup_key(controller, cgroup, "tasks");
849
850 } else
851 k = get_cgroup_key(controller, path1, path2);
852
853 if (!k)
854 return -EINVAL;
855
856 /*
857 * This being a fuse request, the uid and gid must be valid
858 * in the caller's namespace. So we can just check to make
859 * sure that the caller is root in his uid, and privileged
860 * over the file's current owner.
861 */
862 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_OPT))
863 return -EPERM;
864
865 if (!cgm_chmod_file(controller, cgroup, mode))
866 return -EINVAL;
867 return 0;
fd2e4e03
SH
868}
869
ab54b798
SH
870int cg_mkdir(const char *path, mode_t mode)
871{
872 struct fuse_context *fc = fuse_get_context();
873 nih_local struct cgm_keys **list = NULL;
874 char *fpath = NULL, *path1;
875 nih_local char * cgdir = NULL;
876 const char *cgroup;
877 nih_local char *controller = NULL;
878
ab54b798
SH
879 if (!fc)
880 return -EIO;
881
882
883 controller = pick_controller_from_path(fc, path);
884 if (!controller)
f9a05025 885 return -EINVAL;
ab54b798
SH
886
887 cgroup = find_cgroup_in_path(path);
888 if (!cgroup)
f9a05025 889 return -EINVAL;
ab54b798
SH
890
891 get_cgdir_and_path(cgroup, &cgdir, &fpath);
892 if (!fpath)
893 path1 = "/";
894 else
895 path1 = cgdir;
896
897 if (!fc_may_access(fc, controller, path1, NULL, O_RDWR))
f9a05025 898 return -EACCES;
ab54b798
SH
899
900
901 if (!cgm_create(controller, cgroup, fc->uid, fc->gid))
902 return -EINVAL;
903
904 return 0;
905}
906
50d8d5b5
SH
907static int cg_rmdir(const char *path)
908{
909 struct fuse_context *fc = fuse_get_context();
910 nih_local struct cgm_keys **list = NULL;
911 char *fpath = NULL;
912 nih_local char * cgdir = NULL;
913 const char *cgroup;
914 nih_local char *controller = NULL;
915
916 if (!fc)
917 return -EIO;
918
919
920 controller = pick_controller_from_path(fc, path);
921 if (!controller)
f9a05025 922 return -EINVAL;
50d8d5b5
SH
923
924 cgroup = find_cgroup_in_path(path);
925 if (!cgroup)
f9a05025 926 return -EINVAL;
50d8d5b5
SH
927
928 get_cgdir_and_path(cgroup, &cgdir, &fpath);
929 if (!fpath)
930 return -EINVAL;
931
932 if (!fc_may_access(fc, controller, cgdir, NULL, O_WRONLY))
f9a05025 933 return -EACCES;
50d8d5b5
SH
934
935 if (!cgm_remove(controller, cgroup))
936 return -EINVAL;
937
938 return 0;
939}
940
758ad80c 941/*
2ad6d2bd 942 * FUSE ops for /proc
758ad80c 943 */
758ad80c
SH
944
945static int proc_getattr(const char *path, struct stat *sb)
946{
947 if (strcmp(path, "/proc") != 0)
948 return -EINVAL;
949 sb->st_mode = S_IFDIR | 00755;
950 sb->st_nlink = 2;
951 return 0;
952}
953
2ad6d2bd
SH
954/*
955 * FUSE ops for /
956 * these just delegate to the /proc and /cgroup ops as
957 * needed
958 */
758ad80c
SH
959
960static int lxcfs_getattr(const char *path, struct stat *sb)
961{
962 if (strcmp(path, "/") == 0) {
963 sb->st_mode = S_IFDIR | 00755;
964 sb->st_nlink = 2;
965 return 0;
966 }
967 if (strncmp(path, "/cgroup", 7) == 0) {
968 return cg_getattr(path, sb);
969 }
970 if (strncmp(path, "/proc", 7) == 0) {
971 return proc_getattr(path, sb);
972 }
973 return -EINVAL;
974}
975
976static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
977{
978 if (strcmp(path, "/") == 0)
979 return 0;
980
981 if (strncmp(path, "/cgroup", 7) == 0) {
982 return cg_opendir(path, fi);
983 }
984 return -EINVAL;
985}
986
987static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
988 struct fuse_file_info *fi)
989{
990 if (strcmp(path, "/") == 0) {
991 if (filler(buf, "proc", NULL, 0) != 0 ||
992 filler(buf, "cgroup", NULL, 0) != 0)
993 return -EINVAL;
994 return 0;
995 }
996 if (strncmp(path, "/cgroup", 7) == 0) {
997 return cg_readdir(path, buf, filler, offset, fi);
998 }
999 return -EINVAL;
1000}
1001
1002static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
1003{
1004 if (strcmp(path, "/") == 0)
1005 return 0;
1006 if (strncmp(path, "/cgroup", 7) == 0) {
1007 return cg_releasedir(path, fi);
1008 }
1009 return -EINVAL;
1010}
1011
99978832
SH
1012static int lxcfs_open(const char *path, struct fuse_file_info *fi)
1013{
1014 if (strncmp(path, "/cgroup", 7) == 0) {
1015 return cg_open(path, fi);
1016 }
1017
1018 return -EINVAL;
1019}
1020
1021static int lxcfs_read(const char *path, char *buf, size_t size, off_t offset,
1022 struct fuse_file_info *fi)
1023{
1024 if (strncmp(path, "/cgroup", 7) == 0) {
1025 return cg_read(path, buf, size, offset, fi);
1026 }
1027
1028 return -EINVAL;
1029}
1030
2ad6d2bd
SH
1031int lxcfs_write(const char *path, const char *buf, size_t size, off_t offset,
1032 struct fuse_file_info *fi)
1033{
1034 if (strncmp(path, "/cgroup", 7) == 0) {
1035 return cg_write(path, buf, size, offset, fi);
1036 }
1037
1038 return -EINVAL;
1039}
1040
99978832
SH
1041static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
1042{
1043 return 0;
1044}
1045
1046static int lxcfs_release(const char *path, struct fuse_file_info *fi)
758ad80c 1047{
99978832
SH
1048 return 0;
1049}
1050
1051static int lxcfs_fsync(const char *path, int datasync, struct fuse_file_info *fi)
1052{
1053 return 0;
758ad80c
SH
1054}
1055
ab54b798
SH
1056int lxcfs_mkdir(const char *path, mode_t mode)
1057{
1058 if (strncmp(path, "/cgroup", 7) == 0)
1059 return cg_mkdir(path, mode);
1060
1061 return -EINVAL;
1062}
1063
341b21ad
SH
1064int lxcfs_chown(const char *path, uid_t uid, gid_t gid)
1065{
1066 if (strncmp(path, "/cgroup", 7) == 0)
1067 return cg_chown(path, uid, gid);
1068
1069 return -EINVAL;
1070}
1071
2ad6d2bd
SH
1072/*
1073 * cat first does a truncate before doing ops->write. This doesn't
1074 * really make sense for cgroups. So just return 0 always but do
1075 * nothing.
1076 */
1077int lxcfs_truncate(const char *path, off_t newsize)
1078{
1079 if (strncmp(path, "/cgroup", 7) == 0)
1080 return 0;
1081 return -EINVAL;
1082}
1083
50d8d5b5
SH
1084int lxcfs_rmdir(const char *path)
1085{
1086 if (strncmp(path, "/cgroup", 7) == 0)
1087 return cg_rmdir(path);
1088 return -EINVAL;
1089}
1090
fd2e4e03
SH
1091int lxcfs_chmod(const char *path, mode_t mode)
1092{
1093 if (strncmp(path, "/cgroup", 7) == 0)
1094 return cg_chmod(path, mode);
1095 return -EINVAL;
1096}
1097
758ad80c
SH
1098const struct fuse_operations lxcfs_ops = {
1099 .getattr = lxcfs_getattr,
1100 .readlink = NULL,
1101 .getdir = NULL,
1102 .mknod = NULL,
ab54b798 1103 .mkdir = lxcfs_mkdir,
758ad80c 1104 .unlink = NULL,
50d8d5b5 1105 .rmdir = lxcfs_rmdir,
758ad80c
SH
1106 .symlink = NULL,
1107 .rename = NULL,
1108 .link = NULL,
fd2e4e03 1109 .chmod = lxcfs_chmod,
341b21ad 1110 .chown = lxcfs_chown,
2ad6d2bd 1111 .truncate = lxcfs_truncate,
758ad80c 1112 .utime = NULL,
99978832
SH
1113
1114 .open = lxcfs_open,
1115 .read = lxcfs_read,
1116 .release = lxcfs_release,
2ad6d2bd 1117 .write = lxcfs_write,
99978832 1118
758ad80c 1119 .statfs = NULL,
99978832
SH
1120 .flush = lxcfs_flush,
1121 .fsync = lxcfs_fsync,
758ad80c
SH
1122
1123 .setxattr = NULL,
1124 .getxattr = NULL,
1125 .listxattr = NULL,
1126 .removexattr = NULL,
1127
1128 .opendir = lxcfs_opendir,
1129 .readdir = lxcfs_readdir,
1130 .releasedir = lxcfs_releasedir,
1131
1132 .fsyncdir = NULL,
1133 .init = NULL,
1134 .destroy = NULL,
1135 .access = NULL,
1136 .create = NULL,
1137 .ftruncate = NULL,
1138 .fgetattr = NULL,
1139};
1140
99978832 1141static void usage(const char *me)
758ad80c
SH
1142{
1143 fprintf(stderr, "Usage:\n");
1144 fprintf(stderr, "\n");
1145 fprintf(stderr, "%s [FUSE and mount options] mountpoint\n", me);
1146 exit(1);
1147}
1148
99978832 1149static bool is_help(char *w)
758ad80c
SH
1150{
1151 if (strcmp(w, "-h") == 0 ||
1152 strcmp(w, "--help") == 0 ||
1153 strcmp(w, "-help") == 0 ||
1154 strcmp(w, "help") == 0)
1155 return true;
1156 return false;
1157}
1158
1159int main(int argc, char *argv[])
1160{
1161 int ret;
1162 struct lxcfs_state *d;
1163
1164 if (argc < 2 || is_help(argv[1]))
1165 usage(argv[0]);
1166
1167 d = malloc(sizeof(*d));
1168 if (!d)
1169 return -1;
1170
1171 if (!cgm_escape_cgroup())
1172 fprintf(stderr, "WARNING: failed to escape to root cgroup\n");
1173
1174 if (!cgm_get_controllers(&d->subsystems))
1175 return -1;
1176
1177 ret = fuse_main(argc, argv, &lxcfs_ops, d);
1178
1179 return ret;
2183082c 1180}