]> git.proxmox.com Git - mirror_lxcfs.git/blame - lxcfs.c
Implement chown
[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) {
396 fprintf(stderr, "Failed to find a tasks file for %s\n", cgroup);
397 sb->st_uid = sb->st_gid = 0;
398 } else {
399 fprintf(stderr, "found a tasks file for %s\n", cgroup);
400 sb->st_uid = k->uid;
401 sb->st_gid = k->gid;
402 }
758ad80c
SH
403 sb->st_nlink = 2;
404 return 0;
405 }
406
407 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
408 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
409 return -EPERM;
410
758ad80c 411 sb->st_mode = S_IFREG | k->mode;
053a659d 412 sb->st_nlink = 1;
758ad80c
SH
413 sb->st_uid = k->uid;
414 sb->st_gid = k->gid;
99978832 415 sb->st_size = get_file_size(controller, path1, path2);
758ad80c
SH
416 return 0;
417 }
418
ab54b798 419 return -ENOENT;
758ad80c 420}
2183082c 421
758ad80c 422static int cg_opendir(const char *path, struct fuse_file_info *fi)
2183082c 423{
758ad80c
SH
424 return 0;
425}
426
758ad80c
SH
427static int cg_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
428 struct fuse_file_info *fi)
429{
430 struct fuse_context *fc = fuse_get_context();
431
432 if (!fc)
433 return -EIO;
434
435 if (strcmp(path, "/cgroup") == 0) {
436 // get list of controllers
437 char **list = LXCFS_DATA ? LXCFS_DATA->subsystems : NULL;
438 int i;
439
440 if (!list)
441 return -EIO;
442 /* TODO - collect the list of controllers at fuse_init */
443 for (i = 0; list[i]; i++) {
444 if (filler(buf, list[i], NULL, 0) != 0) {
445 return -EIO;
446 }
447 }
448 return 0;
449 }
450
451 // return list of keys for the controller, and list of child cgroups
452 nih_local struct cgm_keys **list = NULL;
453 const char *cgroup;
454 nih_local char *controller = NULL;
455 int i;
456
457 controller = pick_controller_from_path(fc, path);
458 if (!controller)
459 return -EIO;
460
461 cgroup = find_cgroup_in_path(path);
462 if (!cgroup) {
463 /* this is just /cgroup/controller, return its contents */
464 cgroup = "/";
465 }
466
467 if (!fc_may_access(fc, controller, cgroup, NULL, O_RDONLY))
468 return -EPERM;
469
470 if (!cgm_list_keys(controller, cgroup, &list))
471 return -EINVAL;
472 for (i = 0; list[i]; i++) {
758ad80c
SH
473 if (filler(buf, list[i]->name, NULL, 0) != 0) {
474 return -EIO;
475 }
476 }
477
478 // now get the list of child cgroups
479 nih_local char **clist;
480
481 if (!cgm_list_children(controller, cgroup, &clist))
482 return 0;
483 for (i = 0; clist[i]; i++) {
758ad80c
SH
484 if (filler(buf, clist[i], NULL, 0) != 0) {
485 return -EIO;
486 }
487 }
488 return 0;
489}
490
491static int cg_releasedir(const char *path, struct fuse_file_info *fi)
492{
493 return 0;
494}
495
99978832
SH
496static int cg_open(const char *path, struct fuse_file_info *fi)
497{
498 nih_local char *controller = NULL;
499 const char *cgroup;
500 char *fpath = NULL, *path1, *path2;
501 nih_local char * cgdir = NULL;
502 nih_local struct cgm_keys *k = NULL;
503 struct fuse_context *fc = fuse_get_context();
504
505 if (!fc)
506 return -EIO;
507
508 controller = pick_controller_from_path(fc, path);
509 if (!controller)
510 return -EIO;
511 cgroup = find_cgroup_in_path(path);
512 if (!cgroup)
513 return -EINVAL;
514
515 get_cgdir_and_path(cgroup, &cgdir, &fpath);
516 if (!fpath) {
517 path1 = "/";
518 path2 = cgdir;
519 } else {
520 path1 = cgdir;
521 path2 = fpath;
522 }
523
524 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
525 if (!fc_may_access(fc, controller, path1, path2, fi->flags))
526 return -EPERM;
527
528 /* TODO - we want to cache this info for read/write */
529 return 0;
530 }
531
532 return -EINVAL;
533}
534
535static int cg_read(const char *path, char *buf, size_t size, off_t offset,
536 struct fuse_file_info *fi)
537{
538 nih_local char *controller = NULL;
539 const char *cgroup;
540 char *fpath = NULL, *path1, *path2;
541 struct fuse_context *fc = fuse_get_context();
542 nih_local char * cgdir = NULL;
543 nih_local struct cgm_keys *k = NULL;
544
545 if (offset)
546 return -EIO;
547
548 if (!fc)
549 return -EIO;
550
551 controller = pick_controller_from_path(fc, path);
552 if (!controller)
553 return -EIO;
554 cgroup = find_cgroup_in_path(path);
555 if (!cgroup)
556 return -EINVAL;
557
558 get_cgdir_and_path(cgroup, &cgdir, &fpath);
559 if (!fpath) {
560 path1 = "/";
561 path2 = cgdir;
562 } else {
563 path1 = cgdir;
564 path2 = fpath;
565 }
566
567 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
568 nih_local char *data = NULL;
569 int s;
570
2ad6d2bd 571 if (!fc_may_access(fc, controller, path1, path2, O_RDONLY))
99978832
SH
572 return -EPERM;
573
574 if (!cgm_get_value(controller, path1, path2, &data))
575 return -EINVAL;
576
577 s = strlen(data);
578 if (s > size)
579 s = size;
580 memcpy(buf, data, s);
581
99978832
SH
582 return s;
583 }
584
585 return -EINVAL;
586}
587
2ad6d2bd
SH
588int cg_write(const char *path, const char *buf, size_t size, off_t offset,
589 struct fuse_file_info *fi)
590{
591 nih_local char *controller = NULL;
592 const char *cgroup;
593 char *fpath = NULL, *path1, *path2;
594 struct fuse_context *fc = fuse_get_context();
595 nih_local char * cgdir = NULL;
596 nih_local struct cgm_keys *k = NULL;
597
598fprintf(stderr, "cg_write: starting\n");
599
600 if (offset)
601 return -EIO;
602
603 if (!fc)
604 return -EIO;
605
606 controller = pick_controller_from_path(fc, path);
607 if (!controller)
608 return -EIO;
609 cgroup = find_cgroup_in_path(path);
610 if (!cgroup)
611 return -EINVAL;
612
613 get_cgdir_and_path(cgroup, &cgdir, &fpath);
614 if (!fpath) {
615 path1 = "/";
616 path2 = cgdir;
617 } else {
618 path1 = cgdir;
619 path2 = fpath;
620 }
621
622 if ((k = get_cgroup_key(controller, path1, path2)) != NULL) {
623 if (!fc_may_access(fc, controller, path1, path2, O_WRONLY))
624 return -EPERM;
625
626 if (!cgm_set_value(controller, path1, path2, buf))
627 return -EINVAL;
628
629 return size;
630 }
631
632 return -EINVAL;
633}
634
341b21ad
SH
635int cg_chown(const char *path, uid_t uid, gid_t gid)
636{
637 struct fuse_context *fc = fuse_get_context();
638 nih_local char * cgdir = NULL;
639 char *fpath = NULL, *path1, *path2;
640 nih_local struct cgm_keys *k = NULL;
641 const char *cgroup;
642 nih_local char *controller = NULL;
643
644
645 if (!fc)
646 return -EIO;
647
648 if (strcmp(path, "/cgroup") == 0)
649 return -EINVAL;
650
651 controller = pick_controller_from_path(fc, path);
652 if (!controller)
653 return -EIO;
654 cgroup = find_cgroup_in_path(path);
655 if (!cgroup)
656 /* this is just /cgroup/controller */
657 return -EINVAL;
658
659 get_cgdir_and_path(cgroup, &cgdir, &fpath);
660
661 if (!fpath) {
662 path1 = "/";
663 path2 = cgdir;
664 } else {
665 path1 = cgdir;
666 path2 = fpath;
667 }
668
669 if (is_child_cgroup(controller, path1, path2)) {
670 // get uid, gid, from '/tasks' file and make up a mode
671 // That is a hack, until cgmanager gains a GetCgroupPerms fn.
672 k = get_cgroup_key(controller, cgroup, "tasks");
673
674 } else
675 k = get_cgroup_key(controller, path1, path2);
676
677 if (!k)
678 return -EINVAL;
679
680 /*
681 * This being a fuse request, the uid and gid must be valid
682 * in the caller's namespace. So we can just check to make
683 * sure that the caller is root in his uid, and privileged
684 * over the file's current owner.
685 */
686 if (!is_privileged_over(fc->pid, fc->uid, k->uid, NS_ROOT_REQD))
687 return -EPERM;
688
689 if (!cgm_chown_file(controller, cgroup, uid, gid))
690 return -EINVAL;
691 return 0;
692}
2ad6d2bd 693
ab54b798
SH
694int cg_mkdir(const char *path, mode_t mode)
695{
696 struct fuse_context *fc = fuse_get_context();
697 nih_local struct cgm_keys **list = NULL;
698 char *fpath = NULL, *path1;
699 nih_local char * cgdir = NULL;
700 const char *cgroup;
701 nih_local char *controller = NULL;
702
ab54b798
SH
703 if (!fc)
704 return -EIO;
705
706
707 controller = pick_controller_from_path(fc, path);
708 if (!controller)
709 return -EIO;
710
711 cgroup = find_cgroup_in_path(path);
712 if (!cgroup)
713 return -EIO;
714
715 get_cgdir_and_path(cgroup, &cgdir, &fpath);
716 if (!fpath)
717 path1 = "/";
718 else
719 path1 = cgdir;
720
721 if (!fc_may_access(fc, controller, path1, NULL, O_RDWR))
722 return -EPERM;
723
724
725 if (!cgm_create(controller, cgroup, fc->uid, fc->gid))
726 return -EINVAL;
727
728 return 0;
729}
730
758ad80c 731/*
2ad6d2bd 732 * FUSE ops for /proc
758ad80c 733 */
758ad80c
SH
734
735static int proc_getattr(const char *path, struct stat *sb)
736{
737 if (strcmp(path, "/proc") != 0)
738 return -EINVAL;
739 sb->st_mode = S_IFDIR | 00755;
740 sb->st_nlink = 2;
741 return 0;
742}
743
2ad6d2bd
SH
744/*
745 * FUSE ops for /
746 * these just delegate to the /proc and /cgroup ops as
747 * needed
748 */
758ad80c
SH
749
750static int lxcfs_getattr(const char *path, struct stat *sb)
751{
752 if (strcmp(path, "/") == 0) {
753 sb->st_mode = S_IFDIR | 00755;
754 sb->st_nlink = 2;
755 return 0;
756 }
757 if (strncmp(path, "/cgroup", 7) == 0) {
758 return cg_getattr(path, sb);
759 }
760 if (strncmp(path, "/proc", 7) == 0) {
761 return proc_getattr(path, sb);
762 }
763 return -EINVAL;
764}
765
766static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
767{
768 if (strcmp(path, "/") == 0)
769 return 0;
770
771 if (strncmp(path, "/cgroup", 7) == 0) {
772 return cg_opendir(path, fi);
773 }
774 return -EINVAL;
775}
776
777static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
778 struct fuse_file_info *fi)
779{
780 if (strcmp(path, "/") == 0) {
781 if (filler(buf, "proc", NULL, 0) != 0 ||
782 filler(buf, "cgroup", NULL, 0) != 0)
783 return -EINVAL;
784 return 0;
785 }
786 if (strncmp(path, "/cgroup", 7) == 0) {
787 return cg_readdir(path, buf, filler, offset, fi);
788 }
789 return -EINVAL;
790}
791
792static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
793{
794 if (strcmp(path, "/") == 0)
795 return 0;
796 if (strncmp(path, "/cgroup", 7) == 0) {
797 return cg_releasedir(path, fi);
798 }
799 return -EINVAL;
800}
801
99978832
SH
802static int lxcfs_open(const char *path, struct fuse_file_info *fi)
803{
804 if (strncmp(path, "/cgroup", 7) == 0) {
805 return cg_open(path, fi);
806 }
807
808 return -EINVAL;
809}
810
811static int lxcfs_read(const char *path, char *buf, size_t size, off_t offset,
812 struct fuse_file_info *fi)
813{
814 if (strncmp(path, "/cgroup", 7) == 0) {
815 return cg_read(path, buf, size, offset, fi);
816 }
817
818 return -EINVAL;
819}
820
2ad6d2bd
SH
821int lxcfs_write(const char *path, const char *buf, size_t size, off_t offset,
822 struct fuse_file_info *fi)
823{
824 if (strncmp(path, "/cgroup", 7) == 0) {
825 return cg_write(path, buf, size, offset, fi);
826 }
827
828 return -EINVAL;
829}
830
99978832
SH
831static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
832{
833 return 0;
834}
835
836static int lxcfs_release(const char *path, struct fuse_file_info *fi)
758ad80c 837{
99978832
SH
838 return 0;
839}
840
841static int lxcfs_fsync(const char *path, int datasync, struct fuse_file_info *fi)
842{
843 return 0;
758ad80c
SH
844}
845
ab54b798
SH
846int lxcfs_mkdir(const char *path, mode_t mode)
847{
848 if (strncmp(path, "/cgroup", 7) == 0)
849 return cg_mkdir(path, mode);
850
851 return -EINVAL;
852}
853
341b21ad
SH
854int lxcfs_chown(const char *path, uid_t uid, gid_t gid)
855{
856 if (strncmp(path, "/cgroup", 7) == 0)
857 return cg_chown(path, uid, gid);
858
859 return -EINVAL;
860}
861
2ad6d2bd
SH
862/*
863 * cat first does a truncate before doing ops->write. This doesn't
864 * really make sense for cgroups. So just return 0 always but do
865 * nothing.
866 */
867int lxcfs_truncate(const char *path, off_t newsize)
868{
869 if (strncmp(path, "/cgroup", 7) == 0)
870 return 0;
871 return -EINVAL;
872}
873
758ad80c
SH
874const struct fuse_operations lxcfs_ops = {
875 .getattr = lxcfs_getattr,
876 .readlink = NULL,
877 .getdir = NULL,
878 .mknod = NULL,
ab54b798 879 .mkdir = lxcfs_mkdir,
758ad80c
SH
880 .unlink = NULL,
881 .rmdir = NULL,
882 .symlink = NULL,
883 .rename = NULL,
884 .link = NULL,
885 .chmod = NULL,
341b21ad 886 .chown = lxcfs_chown,
2ad6d2bd 887 .truncate = lxcfs_truncate,
758ad80c 888 .utime = NULL,
99978832
SH
889
890 .open = lxcfs_open,
891 .read = lxcfs_read,
892 .release = lxcfs_release,
2ad6d2bd 893 .write = lxcfs_write,
99978832 894
758ad80c 895 .statfs = NULL,
99978832
SH
896 .flush = lxcfs_flush,
897 .fsync = lxcfs_fsync,
758ad80c
SH
898
899 .setxattr = NULL,
900 .getxattr = NULL,
901 .listxattr = NULL,
902 .removexattr = NULL,
903
904 .opendir = lxcfs_opendir,
905 .readdir = lxcfs_readdir,
906 .releasedir = lxcfs_releasedir,
907
908 .fsyncdir = NULL,
909 .init = NULL,
910 .destroy = NULL,
911 .access = NULL,
912 .create = NULL,
913 .ftruncate = NULL,
914 .fgetattr = NULL,
915};
916
99978832 917static void usage(const char *me)
758ad80c
SH
918{
919 fprintf(stderr, "Usage:\n");
920 fprintf(stderr, "\n");
921 fprintf(stderr, "%s [FUSE and mount options] mountpoint\n", me);
922 exit(1);
923}
924
99978832 925static bool is_help(char *w)
758ad80c
SH
926{
927 if (strcmp(w, "-h") == 0 ||
928 strcmp(w, "--help") == 0 ||
929 strcmp(w, "-help") == 0 ||
930 strcmp(w, "help") == 0)
931 return true;
932 return false;
933}
934
935int main(int argc, char *argv[])
936{
937 int ret;
938 struct lxcfs_state *d;
939
940 if (argc < 2 || is_help(argv[1]))
941 usage(argv[0]);
942
943 d = malloc(sizeof(*d));
944 if (!d)
945 return -1;
946
947 if (!cgm_escape_cgroup())
948 fprintf(stderr, "WARNING: failed to escape to root cgroup\n");
949
950 if (!cgm_get_controllers(&d->subsystems))
951 return -1;
952
953 ret = fuse_main(argc, argv, &lxcfs_ops, d);
954
955 return ret;
2183082c 956}