stat: switch to user context
[ciopfs.git] / ciopfs.c
blob1c35759fca9d1edc61debd062163bbdc01c77756
1 /*
2 * CIOPFS - The Case Insensitive On Purpose Filesystem for FUSE
4 * (c) 2008 Marc Andre Tanner <mat at brain-dump dot org>
5 * (c) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
7 * This program can be distributed under the terms of the GNU GPLv2.
9 * How it works:
10 * Before any operation takes place all filenames are
11 * converted to lower case. The original filenames are stored
12 * in extended attributes named user.filename. This value
13 * is returned upon request.
15 * Files or directories which aren't all lowercase in the
16 * underlying file system are ignored. You should probably
17 * start with an empty data directory and copy your contents
18 * over.
20 * Requirements:
21 * In order to compile ciopfs, you will need both
22 * libfuse and libattr. Furthermore if you want a case
23 * preserving file system you have to make sure that the
24 * underlying file system supports extended attributes
25 * (for example for ext{2,3} you need a kernel with
26 * CONFIG_EXT{2,3}_FS_XATTR enabled). You probably also
27 * want to mount the underlying filesystem with the
28 * user_xattr option which allows non root users to create
29 * extended attributes.
31 * If you want to work with unicode characters within file
32 * names, you will need libicu from www.icu-project.org.
33 * Otherwise disable it in config.mk, the file system will
34 * only work with ascii [a-zA-Z] file names.
36 * Compile & Install:
37 * $EDITOR config.mk
38 * make
39 * sudo make install
41 * Mount:
42 * ciopfs directory mountpoint [options]
46 #ifdef __linux__
47 #define _XOPEN_SOURCE 500 /* For pread()/pwrite() */
48 #endif
50 #define _BSD_SOURCE /* for vsyslog() */
52 #include <fuse.h>
53 #include <ulockmgr.h>
54 #include <sys/xattr.h>
55 #include <sys/time.h>
56 #include <assert.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <stdbool.h>
60 #include <stdarg.h>
61 #include <string.h>
62 #include <errno.h>
63 #include <dirent.h>
64 #include <unistd.h>
65 #include <limits.h>
66 #include <syslog.h>
67 #include <grp.h>
69 #if __GNUC__ >= 3
70 # define likely(x) __builtin_expect(!!(x), 1)
71 # define unlikely(x) __builtin_expect(!!(x), 0)
72 #else
73 # define likely(x) (x)
74 # define unlikely(x) (x)
75 #endif
77 /* each *.c file implements the following two functions:
79 * static inline bool str_contains_upper(const char *s);
80 * static inline char *str_fold(const char *s);
82 #ifdef HAVE_GLIB
83 # include "unicode-glib.c"
84 #elif defined HAVE_LIBICUUC
85 # include "unicode-icu.c"
86 #else
87 # include "ascii.c"
88 #endif
90 #define log_print(format, args...) (*dolog)(format, ## args)
92 #ifdef NDEBUG
93 # define debug(format, args...)
94 #else
95 # define debug log_print
96 #endif
98 #define CIOPFS_ATTR_NAME "user.filename"
100 #ifndef PATH_MAX
101 #define PATH_MAX 4096
102 #endif
104 #ifndef FILENAME_MAX
105 #define FILENAME_MAX 4096
106 #endif
108 static const char *dirname;
109 /* whether we have forced fuse in single threaded mode (`-s' option). This happens
110 * because we can't store uid/gids per thread and the file system is accessible for
111 * multiple users via the `-o allow_other' option.
113 static bool single_threaded = false;
115 void stderr_print(const char *fmt, ...)
117 va_list ap;
118 va_start(ap, fmt);
119 fputs("ciopfs: ", stderr);
120 vfprintf(stderr, fmt, ap);
121 va_end(ap);
124 void syslog_print(const char *fmt, ...)
126 va_list ap;
127 va_start(ap, fmt);
128 vsyslog(LOG_NOTICE, fmt, ap);
129 va_end(ap);
132 static void (*dolog)(const char *fmt, ...) = syslog_print;
134 static char *map_path(const char *path)
136 char *p;
137 /* XXX: memory fragmentation? */
138 if (path[0] == '/') {
139 if (path[1] == '\0')
140 return strdup(".");
141 path++;
144 p = str_fold(path);
145 debug("%s => %s\n", path, p);
146 return p;
149 /* Returns the supplementary group IDs of a calling process which
150 * isued the file system operation.
152 * As indicated by Miklos Szeredi the group list is available in
154 * /proc/$PID/task/$TID/status
156 * and fuse supplies TID in get_fuse_context()->pid.
158 * Jean-Pierre Andre found out that the same information is also
159 * available in
161 * /proc/$TID/task/$TID/status
163 * which is used in this implementation.
166 static size_t get_groups(pid_t pid, gid_t **groups)
168 static char key[] = "\nGroups:\t";
169 char filename[64], buf[2048], *s, *t, c = '\0';
170 int fd, num_read, matched = 0;
171 size_t n = 0;
172 gid_t *gids, grp = 0;
174 sprintf(filename, "/proc/%u/task/%u/status", pid, pid);
175 fd = open(filename, O_RDONLY);
176 if (fd == -1)
177 return 0;
179 for (;;) {
180 if (!c) {
181 num_read = read(fd, buf, sizeof(buf) - 1);
182 if (num_read <= 0) {
183 close(fd);
184 return 0;
186 buf[num_read] = '\0';
187 s = buf;
190 c = *s++;
192 if (key[matched] == c) {
193 if (!key[++matched])
194 break;
196 } else
197 matched = (key[0] == c);
200 close(fd);
201 t = s;
202 n = 0;
204 while (*t != '\n') {
205 if (*t++ == ' ')
206 n++;
209 if (n == 0)
210 return 0;
212 *groups = gids = malloc(n * sizeof(gid_t));
213 if (!gids)
214 return 0;
215 n = 0;
217 while ((c = *s++) != '\n') {
218 if (c >= '0' && c <= '9')
219 grp = grp*10 + c - '0';
220 else if (c == ' ') {
221 gids[n++] = grp;
222 grp = 0;
226 return n;
229 /* This only works when the filesystem is mounted by root and fuse
230 * operates in single threaded mode. Because the euid/egid are stored
231 * per process this would otherwise cause all sorts of race condidtions
232 * and security issues when multiple users access the file system
233 * simultaneously.
236 static inline void enter_user_context_effective()
238 gid_t *groups;
239 size_t ngroups;
240 struct fuse_context *c = fuse_get_context();
242 if (!single_threaded || getuid())
243 return;
244 if ((ngroups = get_groups(c->pid, &groups))) {
245 setgroups(ngroups, groups);
246 free(groups);
249 setegid(c->gid);
250 seteuid(c->uid);
253 static inline void leave_user_context_effective()
255 if (!single_threaded || getuid())
256 return;
258 seteuid(getuid());
259 setegid(getgid());
262 /* access(2) checks the real uid/gid not the effective one
263 * we therefore switch them if run as root and in single
264 * threaded mode.
266 * The real uid/gid are stored per process which is why we
267 * can't change them in multithreaded mode. This would lead
268 * to all sorts of race conditions and security issues when
269 * multiple users access the file system simultaneously.
273 static inline void enter_user_context_real()
275 gid_t *groups;
276 size_t ngroups;
277 struct fuse_context *c = fuse_get_context();
279 if (!single_threaded || geteuid())
280 return;
281 if ((ngroups = get_groups(c->pid, &groups))) {
282 setgroups(ngroups, groups);
283 free(groups);
285 setregid(c->gid, -1);
286 setreuid(c->uid, -1);
289 static inline void leave_user_context_real()
291 if (!single_threaded || geteuid())
292 return;
294 setuid(geteuid());
295 setgid(getegid());
298 static ssize_t ciopfs_get_orig_name(const char *path, char *value, size_t size)
300 ssize_t attrlen;
301 debug("looking up original file name of %s ", path);
302 attrlen = lgetxattr(path, CIOPFS_ATTR_NAME, value, size);
303 if (attrlen > 0) {
304 value[attrlen] = '\0';
305 debug("found %s\n", value);
306 } else {
307 debug("nothing found\n");
309 return attrlen;
312 static int ciopfs_set_orig_name_fd(int fd, const char *origpath)
314 char *filename = strrchr(origpath, '/');
315 if (!filename)
316 filename = (char *)origpath;
317 else
318 filename++;
319 #ifndef NDEBUG
320 char *path = map_path(origpath);
321 if (likely(path != NULL)) {
322 log_print("storing original name '%s' in '%s'\n", filename, path);
323 free(path);
325 #endif
326 if (fsetxattr(fd, CIOPFS_ATTR_NAME, filename, strlen(filename), 0)) {
327 debug("%s\n", strerror(errno));
328 return -errno;
330 return 0;
333 static int ciopfs_set_orig_name_path(const char *path, const char *origpath)
335 char *filename = strrchr(origpath, '/');
336 if (!filename)
337 filename = (char *)origpath;
338 else
339 filename++;
340 debug("storing original name '%s' in '%s'\n", filename, path);
341 /* XXX: setting an extended attribute on a symlink doesn't seem to work (EPERM) */
342 if (lsetxattr(path, CIOPFS_ATTR_NAME, filename, strlen(filename), 0)) {
343 debug("%s\n", strerror(errno));
344 return -errno;
346 return 0;
349 static int ciopfs_remove_orig_name(const char *path)
351 debug("removing original file name of %s\n", path);
352 return lremovexattr(path, CIOPFS_ATTR_NAME);
355 static int ciopfs_getattr(const char *path, struct stat *st_data)
357 char *p = map_path(path);
358 if (unlikely(p == NULL))
359 return -ENOMEM;
360 enter_user_context_effective();
361 int res = lstat(p, st_data);
362 leave_user_context_effective();
363 free(p);
364 return (res == -1) ? -errno : 0;
367 static int ciopfs_fgetattr(const char *path, struct stat *stbuf,
368 struct fuse_file_info *fi)
370 enter_user_context_effective();
371 int res = fstat(fi->fh, stbuf);
372 leave_user_context_effective();
373 if (res == -1)
374 return -errno;
375 return 0;
378 static int ciopfs_readlink(const char *path, char *buf, size_t size)
380 char *p = map_path(path);
381 if (unlikely(p == NULL))
382 return -ENOMEM;
383 enter_user_context_effective();
384 int res = readlink(p, buf, size - 1);
385 leave_user_context_effective();
386 free(p);
387 if (res == -1)
388 return -errno;
389 buf[res] = '\0';
390 return 0;
393 static int ciopfs_opendir(const char *path, struct fuse_file_info *fi)
395 char *p = map_path(path);
396 if (unlikely(p == NULL))
397 return -ENOMEM;
398 enter_user_context_effective();
399 DIR *dp = opendir(p);
400 leave_user_context_effective();
401 free(p);
403 if (dp == NULL)
404 return -errno;
406 fi->fh = (uint64_t)(uintptr_t)dp;
407 return 0;
410 static int ciopfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
411 off_t offset, struct fuse_file_info *fi)
413 int ret = 0;
414 DIR *dp = (DIR *)(uintptr_t)fi->fh;
415 struct dirent *de;
416 char *p = map_path(path);
417 if (unlikely(p == NULL))
418 return -ENOMEM;
419 size_t pathlen = strlen(p);
420 char dnamebuf[PATH_MAX];
421 char attrbuf[FILENAME_MAX];
423 if (pathlen > PATH_MAX) {
424 ret = -ENAMETOOLONG;
425 goto out;
428 strcpy(dnamebuf, p);
430 while ((de = readdir(dp)) != NULL) {
431 struct stat st;
432 char *dname;
433 char *attrlower;
435 /* skip any entry which is not all lower case for now */
436 if (str_contains_upper(de->d_name))
437 continue;
439 memset(&st, 0, sizeof(st));
440 st.st_ino = de->d_ino;
441 st.st_mode = de->d_type << 12;
443 if (!strcmp(".", de->d_name) || !strcmp("..", de->d_name))
444 dname = de->d_name;
445 else {
446 /* check whether there is an original name associated with
447 * this path and if so return it instead of the all lower
448 * case one
450 snprintf(dnamebuf, sizeof dnamebuf, "%s/%s", p, de->d_name);
451 debug("dnamebuf: %s de->d_name: %s\n", dnamebuf, de->d_name);
452 if (ciopfs_get_orig_name(dnamebuf, attrbuf, sizeof attrbuf) > 0) {
453 /* we found an original name now check whether it is
454 * still accurate and if not remove it
456 attrlower = str_fold(attrbuf);
457 if (attrlower && !strcmp(attrlower, de->d_name))
458 dname = attrbuf;
459 else {
460 dname = de->d_name;
461 ciopfs_remove_orig_name(dnamebuf);
463 free(attrlower);
464 } else
465 dname = de->d_name;
467 debug("dname: %s\n", dname);
468 if (filler(buf, dname, &st, 0))
469 break;
472 out:
473 free(p);
474 return ret;
477 static int ciopfs_releasedir(const char *path, struct fuse_file_info *fi)
479 if (fi->fh)
480 closedir((DIR *)(uintptr_t)fi->fh);
481 return 0;
484 static int ciopfs_mknod(const char *path, mode_t mode, dev_t rdev)
486 int res;
487 char *p = map_path(path);
488 if (unlikely(p == NULL))
489 return -ENOMEM;
490 enter_user_context_effective();
491 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
492 is more portable */
493 if (S_ISREG(mode)) {
494 res = open(p, O_CREAT | O_EXCL | O_WRONLY, mode);
495 if (res >= 0) {
496 ciopfs_set_orig_name_fd(res, path);
497 close(res);
499 } else if (S_ISFIFO(mode)) {
500 res = mkfifo(p, mode);
501 } else
502 res = mknod(p, mode, rdev);
503 leave_user_context_effective();
504 free(p);
505 if (res == -1)
506 return -errno;
508 return 0;
511 static int ciopfs_mkdir(const char *path, mode_t mode)
513 int ret = 0;
514 char *p = map_path(path);
515 if (unlikely(p == NULL))
516 return -ENOMEM;
517 enter_user_context_effective();
518 int res = mkdir(p, mode);
519 leave_user_context_effective();
521 if (res == -1) {
522 ret = -errno;
523 goto out;
526 ciopfs_set_orig_name_path(p, path);
527 out:
528 free(p);
529 return ret;
532 static int ciopfs_unlink(const char *path)
534 char *p = map_path(path);
535 if (unlikely(p == NULL))
536 return -ENOMEM;
537 enter_user_context_effective();
538 int res = unlink(p);
539 leave_user_context_effective();
540 free(p);
541 if (res == -1)
542 return -errno;
543 return 0;
546 static int ciopfs_rmdir(const char *path)
548 char *p = map_path(path);
549 if (unlikely(p == NULL))
550 return -ENOMEM;
551 enter_user_context_effective();
552 int res = rmdir(p);
553 leave_user_context_effective();
554 free(p);
555 if (res == -1)
556 return -errno;
557 return 0;
560 static int ciopfs_symlink(const char *from, const char *to)
562 int ret = 0;
563 char *f = map_path(from);
564 char *t = map_path(to);
565 if (unlikely(f == NULL || t == NULL))
566 return -ENOMEM;
567 enter_user_context_effective();
568 int res = symlink(f, t);
569 leave_user_context_effective();
570 if (res == -1) {
571 ret = -errno;
572 goto out;
574 ciopfs_set_orig_name_path(t, to);
575 out:
576 free(f);
577 free(t);
578 return ret;
581 static int ciopfs_rename(const char *from, const char *to)
583 int ret = 0;
584 char *f = map_path(from);
585 char *t = map_path(to);
586 if (unlikely(f == NULL || t == NULL))
587 return -ENOMEM;
588 enter_user_context_effective();
589 int res = rename(f, t);
590 leave_user_context_effective();
591 if (res == -1) {
592 ret = -errno;
593 goto out;
595 ciopfs_set_orig_name_path(t, to);
596 out:
597 free(f);
598 free(t);
599 return ret;
602 static int ciopfs_link(const char *from, const char *to)
604 int ret = 0;
605 char *f = map_path(from);
606 char *t = map_path(to);
607 if (unlikely(f == NULL || t == NULL))
608 return -ENOMEM;
609 enter_user_context_effective();
610 int res = link(f, t);
611 leave_user_context_effective();
612 if (res == -1) {
613 ret = -errno;
614 goto out;
616 ciopfs_set_orig_name_path(t, to);
617 out:
618 free(f);
619 free(t);
620 return ret;
623 static int ciopfs_chmod(const char *path, mode_t mode)
625 char *p = map_path(path);
626 if (unlikely(p == NULL))
627 return -ENOMEM;
628 enter_user_context_effective();
629 int res = chmod(p, mode);
630 leave_user_context_effective();
631 free(p);
632 if (res == -1)
633 return -errno;
634 return 0;
637 static int ciopfs_chown(const char *path, uid_t uid, gid_t gid)
639 char *p = map_path(path);
640 if (unlikely(p == NULL))
641 return -ENOMEM;
642 enter_user_context_effective();
643 int res = lchown(p, uid, gid);
644 leave_user_context_effective();
645 free(p);
646 if (res == -1)
647 return -errno;
648 return 0;
651 static int ciopfs_truncate(const char *path, off_t size)
653 char *p = map_path(path);
654 if (unlikely(p == NULL))
655 return -ENOMEM;
656 enter_user_context_effective();
657 int res = truncate(p, size);
658 leave_user_context_effective();
659 free(p);
660 if (res == -1)
661 return -errno;
662 return 0;
665 static int ciopfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi)
667 enter_user_context_effective();
668 int res = ftruncate(fi->fh, size);
669 leave_user_context_effective();
670 if (res == -1)
671 return -errno;
673 return 0;
676 static int ciopfs_utimens(const char *path, const struct timespec ts[2])
678 char *p = map_path(path);
679 if (unlikely(p == NULL))
680 return -ENOMEM;
681 struct timeval tv[2];
683 tv[0].tv_sec = ts[0].tv_sec;
684 tv[0].tv_usec = ts[0].tv_nsec / 1000;
685 tv[1].tv_sec = ts[1].tv_sec;
686 tv[1].tv_usec = ts[1].tv_nsec / 1000;
688 enter_user_context_effective();
689 int res = utimes(p, tv);
690 leave_user_context_effective();
691 free(p);
692 if (res == -1)
693 return -errno;
694 return 0;
697 static int ciopfs_create(const char *path, mode_t mode, struct fuse_file_info *fi)
699 char *p = map_path(path);
700 if (unlikely(p == NULL))
701 return -ENOMEM;
702 enter_user_context_effective();
703 int fd = open(p, fi->flags, mode);
704 leave_user_context_effective();
705 free(p);
706 if (fd == -1)
707 return -errno;
708 ciopfs_set_orig_name_fd(fd, path);
709 fi->fh = fd;
710 return 0;
713 static int ciopfs_open(const char *path, struct fuse_file_info *fi)
715 char *p = map_path(path);
716 if (unlikely(p == NULL))
717 return -ENOMEM;
718 enter_user_context_effective();
719 int fd = open(p, fi->flags);
720 leave_user_context_effective();
721 free(p);
722 if (fd == -1)
723 return -errno;
724 if (fi->flags & O_CREAT)
725 ciopfs_set_orig_name_fd(fd, path);
726 fi->fh = fd;
727 return 0;
730 static int ciopfs_read(const char *path, char *buf, size_t size, off_t offset,
731 struct fuse_file_info *fi)
733 int res = pread(fi->fh, buf, size, offset);
734 if (res == -1)
735 res = -errno;
736 return res;
739 static int ciopfs_write(const char *path, const char *buf, size_t size,
740 off_t offset, struct fuse_file_info *fi)
742 int res = pwrite(fi->fh, buf, size, offset);
743 if (res == -1)
744 res = -errno;
745 return res;
748 static int ciopfs_statfs(const char *path, struct statvfs *stbuf)
750 char *p = map_path(path);
751 if (unlikely(p == NULL))
752 return -ENOMEM;
753 enter_user_context_effective();
754 int res = statvfs(p, stbuf);
755 leave_user_context_effective();
756 free(p);
757 if (res == -1)
758 return -errno;
760 return 0;
763 static int ciopfs_flush(const char *path, struct fuse_file_info *fi)
765 /* This is called from every close on an open file, so call the
766 close on the underlying filesystem. But since flush may be
767 called multiple times for an open file, this must not really
768 close the file. This is important if used on a network
769 filesystem like NFS which flush the data/metadata on close() */
770 int res = close(dup(fi->fh));
771 if (res == -1)
772 return -errno;
774 return 0;
777 static int ciopfs_release(const char *path, struct fuse_file_info *fi)
779 close(fi->fh);
780 return 0;
783 static int ciopfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi)
785 int res;
786 #ifdef HAVE_FDATASYNC
787 if (isdatasync)
788 res = fdatasync(fi->fh);
789 else
790 #endif
791 res = fsync(fi->fh);
792 if (res == -1)
793 return -errno;
794 return 0;
797 static int ciopfs_access(const char *path, int mode)
799 char *p = map_path(path);
800 if (unlikely(p == NULL))
801 return -ENOMEM;
802 enter_user_context_real();
803 int res = access(p, mode);
804 leave_user_context_real();
805 free(p);
806 if (res == -1)
807 return -errno;
808 return 0;
811 static int ciopfs_setxattr(const char *path, const char *name, const char *value,
812 size_t size, int flags)
814 if (!strcmp(name, CIOPFS_ATTR_NAME)) {
815 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME);
816 return -EPERM;
818 char *p = map_path(path);
819 if (unlikely(p == NULL))
820 return -ENOMEM;
821 enter_user_context_effective();
822 int res = lsetxattr(p, name, value, size, flags);
823 leave_user_context_effective();
824 free(p);
825 if (res == -1)
826 return -errno;
827 return 0;
830 static int ciopfs_getxattr(const char *path, const char *name, char *value, size_t size)
832 char *p = map_path(path);
833 if (unlikely(p == NULL))
834 return -ENOMEM;
835 enter_user_context_effective();
836 int res = lgetxattr(p, name, value, size);
837 leave_user_context_effective();
838 free(p);
839 if (res == -1)
840 return -errno;
841 return res;
844 static int ciopfs_listxattr(const char *path, char *list, size_t size)
846 char *p = map_path(path);
847 if (unlikely(p == NULL))
848 return -ENOMEM;
849 enter_user_context_effective();
850 int res = llistxattr(p, list, size);
851 leave_user_context_effective();
852 free(p);
853 if (res == -1)
854 return -errno;
855 return res;
858 static int ciopfs_removexattr(const char *path, const char *name)
860 if (!strcmp(name, CIOPFS_ATTR_NAME)) {
861 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME);
862 return -EPERM;
864 char *p = map_path(path);
865 if (unlikely(p == NULL))
866 return -ENOMEM;
867 enter_user_context_effective();
868 int res = lremovexattr(p, name);
869 leave_user_context_effective();
870 free(p);
871 if (res == -1)
872 return -errno;
873 return 0;
876 static int ciopfs_lock(const char *path, struct fuse_file_info *fi, int cmd,
877 struct flock *lock)
879 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
880 sizeof(fi->lock_owner));
883 static void *ciopfs_init(struct fuse_conn_info *conn)
885 if (chdir(dirname) == -1) {
886 log_print("init: %s\n", strerror(errno));
887 exit(1);
889 return NULL;
892 struct fuse_operations ciopfs_operations = {
893 .getattr = ciopfs_getattr,
894 .fgetattr = ciopfs_fgetattr,
895 .readlink = ciopfs_readlink,
896 .opendir = ciopfs_opendir,
897 .readdir = ciopfs_readdir,
898 .releasedir = ciopfs_releasedir,
899 .mknod = ciopfs_mknod,
900 .mkdir = ciopfs_mkdir,
901 .symlink = ciopfs_symlink,
902 .unlink = ciopfs_unlink,
903 .rmdir = ciopfs_rmdir,
904 .rename = ciopfs_rename,
905 .link = ciopfs_link,
906 .chmod = ciopfs_chmod,
907 .chown = ciopfs_chown,
908 .truncate = ciopfs_truncate,
909 .ftruncate = ciopfs_ftruncate,
910 .utimens = ciopfs_utimens,
911 .create = ciopfs_create,
912 .open = ciopfs_open,
913 .read = ciopfs_read,
914 .write = ciopfs_write,
915 .statfs = ciopfs_statfs,
916 .flush = ciopfs_flush,
917 .release = ciopfs_release,
918 .fsync = ciopfs_fsync,
919 .access = ciopfs_access,
920 .setxattr = ciopfs_setxattr,
921 .getxattr = ciopfs_getxattr,
922 .listxattr = ciopfs_listxattr,
923 .removexattr = ciopfs_removexattr,
924 .lock = ciopfs_lock,
925 .init = ciopfs_init
928 static void usage(const char *name)
930 fprintf(stderr, "usage: %s directory mountpoint [options]\n"
931 "\n"
932 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
933 "\n"
934 "general options:\n"
935 " -o opt,[opt...] mount options\n"
936 " -h|--help print help\n"
937 " --version print version\n"
938 "\n", name);
942 enum {
943 CIOPFS_OPT_HELP,
944 CIOPFS_OPT_VERSION
947 static int ciopfs_opt_parse(void *data, const char *arg, int key, struct fuse_args *outargs)
949 switch (key) {
950 case FUSE_OPT_KEY_NONOPT:
951 if (!dirname) {
952 /* XXX: realpath(char *s, NULL) is a glibc extension */
953 if (!(dirname = realpath(arg, NULL))) {
954 perror(outargs->argv[0]);
955 exit(1);
957 return 0;
959 return 1;
960 case FUSE_OPT_KEY_OPT:
961 if (arg[0] == '-') {
962 switch (arg[1]) {
963 case 'd':
964 case 'f':
965 dolog = stderr_print;
967 } else if (!strcmp("allow_other", arg)) {
968 /* disable multithreaded mode if the file system
969 * is accessible to multiple users simultanousely
970 * because we can't store uid/gid per thread and
971 * this leads to all sorts of race conditions and
972 * security issues.
974 single_threaded = (getuid() == 0);
976 return 1;
977 case CIOPFS_OPT_HELP:
978 usage(outargs->argv[0]);
979 fuse_opt_add_arg(outargs, "-ho");
980 fuse_main(outargs->argc, outargs->argv, &ciopfs_operations, NULL);
981 exit(0);
982 case CIOPFS_OPT_VERSION:
983 fprintf(stderr, "%s: "VERSION" fuse: %d\n", outargs->argv[0], fuse_version());
984 exit(0);
985 default:
986 fprintf(stderr, "see `%s -h' for usage\n", outargs->argv[0]);
987 exit(1);
989 return 1;
992 static struct fuse_opt ciopfs_opts[] = {
993 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP),
994 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP),
995 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION),
996 FUSE_OPT_END
999 int main(int argc, char *argv[])
1001 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
1002 fuse_opt_parse(&args, &dirname, ciopfs_opts, ciopfs_opt_parse);
1004 if (single_threaded) {
1005 fuse_opt_add_arg(&args, "-s");
1006 log_print("disabling multithreaded mode for root mounted "
1007 "filesystem that is accessible for other users "
1008 "via the `-o allow_other' option\n");
1011 umask(0);
1012 return fuse_main(args.argc, args.argv, &ciopfs_operations, NULL);