Correct erroneous comment regarding realpath availability
[ciopfs.git] / ciopfs.c
blobc3cf4b041d0e0b5d7f6237479167572bb7ca3ae1
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 if (!dp) {
429 ret = -EBADF;
430 goto out;
433 seekdir(dp, offset);
435 while ((de = readdir(dp)) != NULL) {
436 struct stat st;
437 char *dname;
438 char *attrlower;
440 /* skip any entry which is not all lower case for now */
441 if (str_contains_upper(de->d_name))
442 continue;
444 memset(&st, 0, sizeof(st));
445 st.st_ino = de->d_ino;
446 st.st_mode = de->d_type << 12;
448 if (!strcmp(".", de->d_name) || !strcmp("..", de->d_name))
449 dname = de->d_name;
450 else {
451 /* check whether there is an original name associated with
452 * this path and if so return it instead of the all lower
453 * case one
455 snprintf(dnamebuf, sizeof dnamebuf, "%s/%s", p, de->d_name);
456 debug("dnamebuf: %s de->d_name: %s\n", dnamebuf, de->d_name);
457 if (ciopfs_get_orig_name(dnamebuf, attrbuf, sizeof attrbuf) > 0) {
458 /* we found an original name now check whether it is
459 * still accurate and if not remove it
461 attrlower = str_fold(attrbuf);
462 if (attrlower && !strcmp(attrlower, de->d_name))
463 dname = attrbuf;
464 else {
465 dname = de->d_name;
466 ciopfs_remove_orig_name(dnamebuf);
468 free(attrlower);
469 } else
470 dname = de->d_name;
472 debug("dname: %s\n", dname);
473 if (filler(buf, dname, &st, telldir(dp)))
474 break;
477 out:
478 free(p);
479 return ret;
482 static int ciopfs_releasedir(const char *path, struct fuse_file_info *fi)
484 if (fi->fh)
485 closedir((DIR *)(uintptr_t)fi->fh);
486 return 0;
489 static int ciopfs_mknod(const char *path, mode_t mode, dev_t rdev)
491 int res;
492 char *p = map_path(path);
493 if (unlikely(p == NULL))
494 return -ENOMEM;
495 enter_user_context_effective();
496 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
497 is more portable */
498 if (S_ISREG(mode)) {
499 res = open(p, O_CREAT | O_EXCL | O_WRONLY, mode);
500 if (res >= 0) {
501 ciopfs_set_orig_name_fd(res, path);
502 close(res);
504 } else if (S_ISFIFO(mode)) {
505 res = mkfifo(p, mode);
506 } else
507 res = mknod(p, mode, rdev);
508 leave_user_context_effective();
509 free(p);
510 if (res == -1)
511 return -errno;
513 return 0;
516 static int ciopfs_mkdir(const char *path, mode_t mode)
518 int ret = 0;
519 char *p = map_path(path);
520 if (unlikely(p == NULL))
521 return -ENOMEM;
522 enter_user_context_effective();
523 int res = mkdir(p, mode);
524 leave_user_context_effective();
526 if (res == -1) {
527 ret = -errno;
528 goto out;
531 ciopfs_set_orig_name_path(p, path);
532 out:
533 free(p);
534 return ret;
537 static int ciopfs_unlink(const char *path)
539 char *p = map_path(path);
540 if (unlikely(p == NULL))
541 return -ENOMEM;
542 enter_user_context_effective();
543 int res = unlink(p);
544 leave_user_context_effective();
545 free(p);
546 if (res == -1)
547 return -errno;
548 return 0;
551 static int ciopfs_rmdir(const char *path)
553 char *p = map_path(path);
554 if (unlikely(p == NULL))
555 return -ENOMEM;
556 enter_user_context_effective();
557 int res = rmdir(p);
558 leave_user_context_effective();
559 free(p);
560 if (res == -1)
561 return -errno;
562 return 0;
565 static int ciopfs_symlink(const char *from, const char *to)
567 int ret = 0;
568 char *t = map_path(to);
569 if (unlikely(t == NULL))
570 return -ENOMEM;
571 enter_user_context_effective();
572 int res = symlink(from, t);
573 leave_user_context_effective();
574 if (res == -1) {
575 ret = -errno;
576 goto out;
578 ciopfs_set_orig_name_path(t, to);
579 out:
580 free(t);
581 return ret;
584 static int ciopfs_rename(const char *from, const char *to)
586 int ret = 0;
587 char *f = map_path(from);
588 char *t = map_path(to);
589 if (unlikely(f == NULL || t == NULL))
590 return -ENOMEM;
591 enter_user_context_effective();
592 int res = rename(f, t);
593 leave_user_context_effective();
594 if (res == -1) {
595 ret = -errno;
596 goto out;
598 ciopfs_set_orig_name_path(t, to);
599 out:
600 free(f);
601 free(t);
602 return ret;
605 static int ciopfs_link(const char *from, const char *to)
607 int ret = 0;
608 char *f = map_path(from);
609 char *t = map_path(to);
610 if (unlikely(f == NULL || t == NULL))
611 return -ENOMEM;
612 enter_user_context_effective();
613 int res = link(f, t);
614 leave_user_context_effective();
615 if (res == -1) {
616 ret = -errno;
617 goto out;
619 ciopfs_set_orig_name_path(t, to);
620 out:
621 free(f);
622 free(t);
623 return ret;
626 static int ciopfs_chmod(const char *path, mode_t mode)
628 char *p = map_path(path);
629 if (unlikely(p == NULL))
630 return -ENOMEM;
631 enter_user_context_effective();
632 int res = chmod(p, mode);
633 leave_user_context_effective();
634 free(p);
635 if (res == -1)
636 return -errno;
637 return 0;
640 static int ciopfs_chown(const char *path, uid_t uid, gid_t gid)
642 char *p = map_path(path);
643 if (unlikely(p == NULL))
644 return -ENOMEM;
645 enter_user_context_effective();
646 int res = lchown(p, uid, gid);
647 leave_user_context_effective();
648 free(p);
649 if (res == -1)
650 return -errno;
651 return 0;
654 static int ciopfs_truncate(const char *path, off_t size)
656 char *p = map_path(path);
657 if (unlikely(p == NULL))
658 return -ENOMEM;
659 enter_user_context_effective();
660 int res = truncate(p, size);
661 leave_user_context_effective();
662 free(p);
663 if (res == -1)
664 return -errno;
665 return 0;
668 static int ciopfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi)
670 enter_user_context_effective();
671 int res = ftruncate(fi->fh, size);
672 leave_user_context_effective();
673 if (res == -1)
674 return -errno;
676 return 0;
679 static int ciopfs_utimens(const char *path, const struct timespec ts[2])
681 char *p = map_path(path);
682 if (unlikely(p == NULL))
683 return -ENOMEM;
684 struct timeval tv[2];
686 tv[0].tv_sec = ts[0].tv_sec;
687 tv[0].tv_usec = ts[0].tv_nsec / 1000;
688 tv[1].tv_sec = ts[1].tv_sec;
689 tv[1].tv_usec = ts[1].tv_nsec / 1000;
691 enter_user_context_effective();
692 int res = utimes(p, tv);
693 leave_user_context_effective();
694 free(p);
695 if (res == -1)
696 return -errno;
697 return 0;
700 static int ciopfs_create(const char *path, mode_t mode, struct fuse_file_info *fi)
702 char *p = map_path(path);
703 if (unlikely(p == NULL))
704 return -ENOMEM;
705 enter_user_context_effective();
706 int fd = open(p, fi->flags, mode);
707 leave_user_context_effective();
708 free(p);
709 if (fd == -1)
710 return -errno;
711 ciopfs_set_orig_name_fd(fd, path);
712 fi->fh = fd;
713 return 0;
716 static int ciopfs_open(const char *path, struct fuse_file_info *fi)
718 char *p = map_path(path);
719 if (unlikely(p == NULL))
720 return -ENOMEM;
721 enter_user_context_effective();
722 int fd = open(p, fi->flags);
723 leave_user_context_effective();
724 free(p);
725 if (fd == -1)
726 return -errno;
727 if (fi->flags & O_CREAT)
728 ciopfs_set_orig_name_fd(fd, path);
729 fi->fh = fd;
730 return 0;
733 static int ciopfs_read(const char *path, char *buf, size_t size, off_t offset,
734 struct fuse_file_info *fi)
736 int res = pread(fi->fh, buf, size, offset);
737 if (res == -1)
738 res = -errno;
739 return res;
742 static int ciopfs_write(const char *path, const char *buf, size_t size,
743 off_t offset, struct fuse_file_info *fi)
745 int res = pwrite(fi->fh, buf, size, offset);
746 if (res == -1)
747 res = -errno;
748 return res;
751 static int ciopfs_statfs(const char *path, struct statvfs *stbuf)
753 char *p = map_path(path);
754 if (unlikely(p == NULL))
755 return -ENOMEM;
756 enter_user_context_effective();
757 int res = statvfs(p, stbuf);
758 leave_user_context_effective();
759 free(p);
760 if (res == -1)
761 return -errno;
763 return 0;
766 static int ciopfs_flush(const char *path, struct fuse_file_info *fi)
768 /* This is called from every close on an open file, so call the
769 close on the underlying filesystem. But since flush may be
770 called multiple times for an open file, this must not really
771 close the file. This is important if used on a network
772 filesystem like NFS which flush the data/metadata on close() */
773 int res = close(dup(fi->fh));
774 if (res == -1)
775 return -errno;
777 return 0;
780 static int ciopfs_release(const char *path, struct fuse_file_info *fi)
782 close(fi->fh);
783 return 0;
786 static int ciopfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi)
788 int res;
789 #ifdef HAVE_FDATASYNC
790 if (isdatasync)
791 res = fdatasync(fi->fh);
792 else
793 #endif
794 res = fsync(fi->fh);
795 if (res == -1)
796 return -errno;
797 return 0;
800 static int ciopfs_access(const char *path, int mode)
802 char *p = map_path(path);
803 if (unlikely(p == NULL))
804 return -ENOMEM;
805 enter_user_context_real();
806 int res = access(p, mode);
807 leave_user_context_real();
808 free(p);
809 if (res == -1)
810 return -errno;
811 return 0;
814 static int ciopfs_setxattr(const char *path, const char *name, const char *value,
815 size_t size, int flags)
817 if (!strcmp(name, CIOPFS_ATTR_NAME)) {
818 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME);
819 return -EPERM;
821 char *p = map_path(path);
822 if (unlikely(p == NULL))
823 return -ENOMEM;
824 enter_user_context_effective();
825 int res = lsetxattr(p, name, value, size, flags);
826 leave_user_context_effective();
827 free(p);
828 if (res == -1)
829 return -errno;
830 return 0;
833 static int ciopfs_getxattr(const char *path, const char *name, char *value, size_t size)
835 char *p = map_path(path);
836 if (unlikely(p == NULL))
837 return -ENOMEM;
838 enter_user_context_effective();
839 int res = lgetxattr(p, name, value, size);
840 leave_user_context_effective();
841 free(p);
842 if (res == -1)
843 return -errno;
844 return res;
847 static int ciopfs_listxattr(const char *path, char *list, size_t size)
849 char *p = map_path(path);
850 if (unlikely(p == NULL))
851 return -ENOMEM;
852 enter_user_context_effective();
853 int res = llistxattr(p, list, size);
854 leave_user_context_effective();
855 free(p);
856 if (res == -1)
857 return -errno;
858 return res;
861 static int ciopfs_removexattr(const char *path, const char *name)
863 if (!strcmp(name, CIOPFS_ATTR_NAME)) {
864 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME);
865 return -EPERM;
867 char *p = map_path(path);
868 if (unlikely(p == NULL))
869 return -ENOMEM;
870 enter_user_context_effective();
871 int res = lremovexattr(p, name);
872 leave_user_context_effective();
873 free(p);
874 if (res == -1)
875 return -errno;
876 return 0;
879 static int ciopfs_lock(const char *path, struct fuse_file_info *fi, int cmd,
880 struct flock *lock)
882 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
883 sizeof(fi->lock_owner));
886 static void *ciopfs_init(struct fuse_conn_info *conn)
888 if (chdir(dirname) == -1) {
889 log_print("init: %s\n", strerror(errno));
890 exit(1);
892 return NULL;
895 struct fuse_operations ciopfs_operations = {
896 .getattr = ciopfs_getattr,
897 .fgetattr = ciopfs_fgetattr,
898 .readlink = ciopfs_readlink,
899 .opendir = ciopfs_opendir,
900 .readdir = ciopfs_readdir,
901 .releasedir = ciopfs_releasedir,
902 .mknod = ciopfs_mknod,
903 .mkdir = ciopfs_mkdir,
904 .symlink = ciopfs_symlink,
905 .unlink = ciopfs_unlink,
906 .rmdir = ciopfs_rmdir,
907 .rename = ciopfs_rename,
908 .link = ciopfs_link,
909 .chmod = ciopfs_chmod,
910 .chown = ciopfs_chown,
911 .truncate = ciopfs_truncate,
912 .ftruncate = ciopfs_ftruncate,
913 .utimens = ciopfs_utimens,
914 .create = ciopfs_create,
915 .open = ciopfs_open,
916 .read = ciopfs_read,
917 .write = ciopfs_write,
918 .statfs = ciopfs_statfs,
919 .flush = ciopfs_flush,
920 .release = ciopfs_release,
921 .fsync = ciopfs_fsync,
922 .access = ciopfs_access,
923 .setxattr = ciopfs_setxattr,
924 .getxattr = ciopfs_getxattr,
925 .listxattr = ciopfs_listxattr,
926 .removexattr = ciopfs_removexattr,
927 .lock = ciopfs_lock,
928 .init = ciopfs_init
931 static void usage(const char *name)
933 fprintf(stderr, "usage: %s directory mountpoint [options]\n"
934 "\n"
935 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
936 "\n"
937 "general options:\n"
938 " -o opt,[opt...] mount options\n"
939 " -h|--help print help\n"
940 " --version print version\n"
941 "\n", name);
945 enum {
946 CIOPFS_OPT_HELP,
947 CIOPFS_OPT_VERSION
950 static int ciopfs_opt_parse(void *data, const char *arg, int key, struct fuse_args *outargs)
952 switch (key) {
953 case FUSE_OPT_KEY_NONOPT:
954 if (!dirname) {
955 /* realpath(char *s, NULL) is a POSIX.1-2008 extension, originally from GLIBC,
956 and might be unavailible on older non-glibc systems. */
957 if (!(dirname = realpath(arg, NULL))) {
958 perror(outargs->argv[0]);
959 exit(1);
961 return 0;
963 return 1;
964 case FUSE_OPT_KEY_OPT:
965 if (arg[0] == '-') {
966 switch (arg[1]) {
967 case 'd':
968 case 'f':
969 dolog = stderr_print;
971 } else if (!strcmp("allow_other", arg)) {
972 /* disable multithreaded mode if the file system
973 * is accessible to multiple users simultanousely
974 * because we can't store uid/gid per thread and
975 * this leads to all sorts of race conditions and
976 * security issues.
978 single_threaded = (getuid() == 0);
980 return 1;
981 case CIOPFS_OPT_HELP:
982 usage(outargs->argv[0]);
983 fuse_opt_add_arg(outargs, "-ho");
984 fuse_main(outargs->argc, outargs->argv, &ciopfs_operations, NULL);
985 exit(0);
986 case CIOPFS_OPT_VERSION:
987 fprintf(stderr, "%s: "VERSION" fuse: %d\n", outargs->argv[0], fuse_version());
988 exit(0);
989 default:
990 fprintf(stderr, "see `%s -h' for usage\n", outargs->argv[0]);
991 exit(1);
993 return 1;
996 static struct fuse_opt ciopfs_opts[] = {
997 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP),
998 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP),
999 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION),
1000 FUSE_OPT_END
1003 int main(int argc, char *argv[])
1005 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
1006 fuse_opt_parse(&args, &dirname, ciopfs_opts, ciopfs_opt_parse);
1008 if (single_threaded) {
1009 fuse_opt_add_arg(&args, "-s");
1010 log_print("disabling multithreaded mode for root mounted "
1011 "filesystem that is accessible for other users "
1012 "via the `-o allow_other' option\n");
1015 umask(0);
1016 return fuse_main(args.argc, args.argv, &ciopfs_operations, NULL);