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