Print a warning if the underlying filesystem doesnn't support xattrs
[ciopfs.git] / ciopfs.c
blobd11fd77b76801cd65578c5cd9d58a65c8ca897a9
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 int ret = -errno;
328 debug("%s\n", strerror(errno));
329 return ret;
331 return 0;
334 static int ciopfs_set_orig_name_path(const char *path, const char *origpath)
336 char *filename = strrchr(origpath, '/');
337 if (!filename)
338 filename = (char *)origpath;
339 else
340 filename++;
341 debug("storing original name '%s' in '%s'\n", filename, path);
342 /* XXX: setting an extended attribute on a symlink doesn't seem to work (EPERM) */
343 if (lsetxattr(path, CIOPFS_ATTR_NAME, filename, strlen(filename), 0)) {
344 int ret = -errno;
345 debug("%s\n", strerror(errno));
346 return ret;
348 return 0;
351 static int ciopfs_remove_orig_name(const char *path)
353 debug("removing original file name of %s\n", path);
354 return lremovexattr(path, CIOPFS_ATTR_NAME);
357 static int ciopfs_getattr(const char *path, struct stat *st_data)
359 char *p = map_path(path);
360 if (unlikely(p == NULL))
361 return -ENOMEM;
362 enter_user_context_effective();
363 int res = lstat(p, st_data);
364 if (res == -1)
365 res = -errno;
366 leave_user_context_effective();
367 free(p);
368 return res;
371 static int ciopfs_fgetattr(const char *path, struct stat *stbuf,
372 struct fuse_file_info *fi)
374 enter_user_context_effective();
375 int res = fstat(fi->fh, stbuf);
376 if (res == -1)
377 res = -errno;
378 leave_user_context_effective();
379 return res;
382 static int ciopfs_readlink(const char *path, char *buf, size_t size)
384 int ret;
385 char *p = map_path(path);
386 if (unlikely(p == NULL))
387 return -ENOMEM;
388 enter_user_context_effective();
389 int res = readlink(p, buf, size - 1);
390 if (res == -1)
391 ret = -errno;
392 leave_user_context_effective();
393 free(p);
394 if (res == -1)
395 return ret;
396 buf[res] = '\0';
397 return 0;
400 static int ciopfs_opendir(const char *path, struct fuse_file_info *fi)
402 int ret;
403 char *p = map_path(path);
404 if (unlikely(p == NULL))
405 return -ENOMEM;
406 enter_user_context_effective();
407 DIR *dp = opendir(p);
408 if (dp == NULL)
409 ret = -errno;
410 leave_user_context_effective();
411 free(p);
412 if (dp == NULL)
413 return ret;
414 fi->fh = (uint64_t)(uintptr_t)dp;
415 return 0;
418 static int ciopfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
419 off_t offset, struct fuse_file_info *fi)
421 int ret = 0;
422 DIR *dp = (DIR *)(uintptr_t)fi->fh;
423 struct dirent *de;
424 char *p = map_path(path);
425 if (unlikely(p == NULL))
426 return -ENOMEM;
427 size_t pathlen = strlen(p);
428 char dnamebuf[PATH_MAX];
429 char attrbuf[FILENAME_MAX];
431 if (pathlen > PATH_MAX) {
432 ret = -ENAMETOOLONG;
433 goto out;
436 if (!dp) {
437 ret = -EBADF;
438 goto out;
441 seekdir(dp, offset);
443 while ((de = readdir(dp)) != NULL) {
444 struct stat st;
445 char *dname;
446 char *attrlower;
448 /* skip any entry which is not all lower case for now */
449 if (str_contains_upper(de->d_name))
450 continue;
452 memset(&st, 0, sizeof(st));
453 st.st_ino = de->d_ino;
454 st.st_mode = de->d_type << 12;
456 if (!strcmp(".", de->d_name) || !strcmp("..", de->d_name))
457 dname = de->d_name;
458 else {
459 /* check whether there is an original name associated with
460 * this path and if so return it instead of the all lower
461 * case one
463 snprintf(dnamebuf, sizeof dnamebuf, "%s/%s", p, de->d_name);
464 debug("dnamebuf: %s de->d_name: %s\n", dnamebuf, de->d_name);
465 if (ciopfs_get_orig_name(dnamebuf, attrbuf, sizeof attrbuf) > 0) {
466 /* we found an original name now check whether it is
467 * still accurate and if not remove it
469 attrlower = str_fold(attrbuf);
470 if (attrlower && !strcmp(attrlower, de->d_name))
471 dname = attrbuf;
472 else {
473 dname = de->d_name;
474 ciopfs_remove_orig_name(dnamebuf);
476 free(attrlower);
477 } else
478 dname = de->d_name;
480 debug("dname: %s\n", dname);
481 if (filler(buf, dname, &st, telldir(dp)))
482 break;
485 out:
486 free(p);
487 return ret;
490 static int ciopfs_releasedir(const char *path, struct fuse_file_info *fi)
492 if (fi->fh)
493 closedir((DIR *)(uintptr_t)fi->fh);
494 return 0;
497 static int ciopfs_mknod(const char *path, mode_t mode, dev_t rdev)
499 int res;
500 char *p = map_path(path);
501 if (unlikely(p == NULL))
502 return -ENOMEM;
503 enter_user_context_effective();
504 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
505 is more portable */
506 if (S_ISREG(mode)) {
507 res = open(p, O_CREAT | O_EXCL | O_WRONLY, mode);
508 if (res >= 0) {
509 ciopfs_set_orig_name_fd(res, path);
510 close(res);
512 } else if (S_ISFIFO(mode)) {
513 res = mkfifo(p, mode);
514 } else {
515 res = mknod(p, mode, rdev);
517 if (res == -1)
518 res = -errno;
519 leave_user_context_effective();
520 free(p);
521 return res;
524 static int ciopfs_mkdir(const char *path, mode_t mode)
526 char *p = map_path(path);
527 if (unlikely(p == NULL))
528 return -ENOMEM;
529 enter_user_context_effective();
530 int res = mkdir(p, mode);
531 if (res == -1)
532 res = -errno;
533 leave_user_context_effective();
534 if (res == 0)
535 ciopfs_set_orig_name_path(p, path);
536 free(p);
537 return res;
540 static int ciopfs_unlink(const char *path)
542 char *p = map_path(path);
543 if (unlikely(p == NULL))
544 return -ENOMEM;
545 enter_user_context_effective();
546 int res = unlink(p);
547 if (res == -1)
548 res = -errno;
549 leave_user_context_effective();
550 free(p);
551 return res;
554 static int ciopfs_rmdir(const char *path)
556 char *p = map_path(path);
557 if (unlikely(p == NULL))
558 return -ENOMEM;
559 enter_user_context_effective();
560 int res = rmdir(p);
561 if (res == -1)
562 res = -errno;
563 leave_user_context_effective();
564 free(p);
565 return res;
568 static int ciopfs_symlink(const char *from, const char *to)
570 char *t = map_path(to);
571 if (unlikely(t == NULL))
572 return -ENOMEM;
573 enter_user_context_effective();
574 int res = symlink(from, t);
575 if (res == -1)
576 res = -errno;
577 leave_user_context_effective();
578 if (res == 0)
579 ciopfs_set_orig_name_path(t, to);
580 free(t);
581 return res;
584 static int ciopfs_rename(const char *from, const char *to)
586 char *f = map_path(from);
587 char *t = map_path(to);
588 if (unlikely(f == NULL || t == NULL))
589 return -ENOMEM;
590 enter_user_context_effective();
591 int res = rename(f, t);
592 if (res == -1)
593 res = -errno;
594 leave_user_context_effective();
595 if (res == 0)
596 ciopfs_set_orig_name_path(t, to);
597 free(f);
598 free(t);
599 return res;
602 static int ciopfs_link(const char *from, const char *to)
604 char *f = map_path(from);
605 char *t = map_path(to);
606 if (unlikely(f == NULL || t == NULL))
607 return -ENOMEM;
608 enter_user_context_effective();
609 int res = link(f, t);
610 if (res == -1)
611 res = -errno;
612 leave_user_context_effective();
613 if (res == 0)
614 ciopfs_set_orig_name_path(t, to);
615 free(f);
616 free(t);
617 return res;
620 static int ciopfs_chmod(const char *path, mode_t mode)
622 char *p = map_path(path);
623 if (unlikely(p == NULL))
624 return -ENOMEM;
625 enter_user_context_effective();
626 int res = chmod(p, mode);
627 if (res == -1)
628 res = -errno;
629 leave_user_context_effective();
630 free(p);
631 return res;
634 static int ciopfs_chown(const char *path, uid_t uid, gid_t gid)
636 char *p = map_path(path);
637 if (unlikely(p == NULL))
638 return -ENOMEM;
639 enter_user_context_effective();
640 int res = lchown(p, uid, gid);
641 if (res == -1)
642 res = -errno;
643 leave_user_context_effective();
644 free(p);
645 return res;
648 static int ciopfs_truncate(const char *path, off_t size)
650 char *p = map_path(path);
651 if (unlikely(p == NULL))
652 return -ENOMEM;
653 enter_user_context_effective();
654 int res = truncate(p, size);
655 if (res == -1)
656 res = -errno;
657 leave_user_context_effective();
658 free(p);
659 return res;
662 static int ciopfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi)
664 enter_user_context_effective();
665 int res = ftruncate(fi->fh, size);
666 if (res == -1)
667 res = -errno;
668 leave_user_context_effective();
669 return res;
672 static int ciopfs_utimens(const char *path, const struct timespec ts[2])
674 char *p = map_path(path);
675 if (unlikely(p == NULL))
676 return -ENOMEM;
678 struct timeval tv[2];
679 tv[0].tv_sec = ts[0].tv_sec;
680 tv[0].tv_usec = ts[0].tv_nsec / 1000;
681 tv[1].tv_sec = ts[1].tv_sec;
682 tv[1].tv_usec = ts[1].tv_nsec / 1000;
684 enter_user_context_effective();
685 int res = utimes(p, tv);
686 if (res == -1)
687 res = -errno;
688 leave_user_context_effective();
689 free(p);
690 return res;
693 static int ciopfs_create(const char *path, mode_t mode, struct fuse_file_info *fi)
695 int ret;
696 char *p = map_path(path);
697 if (unlikely(p == NULL))
698 return -ENOMEM;
699 enter_user_context_effective();
700 int fd = open(p, fi->flags, mode);
701 if (fd == -1)
702 ret = -errno;
703 leave_user_context_effective();
704 free(p);
705 if (fd == -1)
706 return ret;
707 ciopfs_set_orig_name_fd(fd, path);
708 fi->fh = fd;
709 return 0;
712 static int ciopfs_open(const char *path, struct fuse_file_info *fi)
714 int ret;
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 if (fd == -1)
721 ret = -errno;
722 leave_user_context_effective();
723 free(p);
724 if (fd == -1)
725 return ret;
726 if (fi->flags & O_CREAT)
727 ciopfs_set_orig_name_fd(fd, path);
728 fi->fh = fd;
729 return 0;
732 static int ciopfs_read(const char *path, char *buf, size_t size, off_t offset,
733 struct fuse_file_info *fi)
735 int res = pread(fi->fh, buf, size, offset);
736 if (res == -1)
737 res = -errno;
738 return res;
741 static int ciopfs_write(const char *path, const char *buf, size_t size,
742 off_t offset, struct fuse_file_info *fi)
744 int res = pwrite(fi->fh, buf, size, offset);
745 if (res == -1)
746 res = -errno;
747 return res;
750 static int ciopfs_statfs(const char *path, struct statvfs *stbuf)
752 char *p = map_path(path);
753 if (unlikely(p == NULL))
754 return -ENOMEM;
755 enter_user_context_effective();
756 int res = statvfs(p, stbuf);
757 if (res == -1)
758 res = -errno;
759 leave_user_context_effective();
760 free(p);
761 return res;
764 static int ciopfs_flush(const char *path, struct fuse_file_info *fi)
766 /* This is called from every close on an open file, so call the
767 close on the underlying filesystem. But since flush may be
768 called multiple times for an open file, this must not really
769 close the file. This is important if used on a network
770 filesystem like NFS which flush the data/metadata on close() */
771 int res = close(dup(fi->fh));
772 if (res == -1)
773 return -errno;
775 return 0;
778 static int ciopfs_release(const char *path, struct fuse_file_info *fi)
780 close(fi->fh);
781 return 0;
784 static int ciopfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi)
786 int res;
787 #ifdef HAVE_FDATASYNC
788 if (isdatasync)
789 res = fdatasync(fi->fh);
790 else
791 #endif
792 res = fsync(fi->fh);
793 if (res == -1)
794 return -errno;
795 return 0;
798 static int ciopfs_access(const char *path, int mode)
800 char *p = map_path(path);
801 if (unlikely(p == NULL))
802 return -ENOMEM;
803 enter_user_context_real();
804 int res = access(p, mode);
805 if (res == -1)
806 res = -errno;
807 leave_user_context_real();
808 free(p);
809 return res;
812 static int ciopfs_setxattr(const char *path, const char *name, const char *value,
813 size_t size, int flags)
815 if (!strcmp(name, CIOPFS_ATTR_NAME)) {
816 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME);
817 return -EPERM;
819 char *p = map_path(path);
820 if (unlikely(p == NULL))
821 return -ENOMEM;
822 enter_user_context_effective();
823 int res = lsetxattr(p, name, value, size, flags);
824 if (res == -1)
825 res = -errno;
826 leave_user_context_effective();
827 free(p);
828 return res;
831 static int ciopfs_getxattr(const char *path, const char *name, char *value, size_t size)
833 char *p = map_path(path);
834 if (unlikely(p == NULL))
835 return -ENOMEM;
836 enter_user_context_effective();
837 int res = lgetxattr(p, name, value, size);
838 if (res == -1)
839 res = -errno;
840 leave_user_context_effective();
841 free(p);
842 return res;
845 static int ciopfs_listxattr(const char *path, char *list, size_t size)
847 char *p = map_path(path);
848 if (unlikely(p == NULL))
849 return -ENOMEM;
850 enter_user_context_effective();
851 int res = llistxattr(p, list, size);
852 if (res == -1)
853 res = -errno;
854 leave_user_context_effective();
855 free(p);
856 return res;
859 static int ciopfs_removexattr(const char *path, const char *name)
861 if (!strcmp(name, CIOPFS_ATTR_NAME)) {
862 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME);
863 return -EPERM;
865 char *p = map_path(path);
866 if (unlikely(p == NULL))
867 return -ENOMEM;
868 enter_user_context_effective();
869 int res = lremovexattr(p, name);
870 if (res == -1)
871 res = -errno;
872 leave_user_context_effective();
873 free(p);
874 return res;
877 static int ciopfs_lock(const char *path, struct fuse_file_info *fi, int cmd,
878 struct flock *lock)
880 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
881 sizeof(fi->lock_owner));
884 static void *ciopfs_init(struct fuse_conn_info *conn)
886 if (chdir(dirname) == -1) {
887 log_print("init: %s\n", strerror(errno));
888 exit(1);
891 if (lsetxattr(".", "user.ciopfs", VERSION, sizeof(VERSION) -1, 0) == -1 && errno == ENOTSUP)
892 log_print("warning underlying filesystem does not support extended attributes, "
893 "converting all filenames to lower case\n");
895 return NULL;
898 struct fuse_operations ciopfs_operations = {
899 .getattr = ciopfs_getattr,
900 .fgetattr = ciopfs_fgetattr,
901 .readlink = ciopfs_readlink,
902 .opendir = ciopfs_opendir,
903 .readdir = ciopfs_readdir,
904 .releasedir = ciopfs_releasedir,
905 .mknod = ciopfs_mknod,
906 .mkdir = ciopfs_mkdir,
907 .symlink = ciopfs_symlink,
908 .unlink = ciopfs_unlink,
909 .rmdir = ciopfs_rmdir,
910 .rename = ciopfs_rename,
911 .link = ciopfs_link,
912 .chmod = ciopfs_chmod,
913 .chown = ciopfs_chown,
914 .truncate = ciopfs_truncate,
915 .ftruncate = ciopfs_ftruncate,
916 .utimens = ciopfs_utimens,
917 .create = ciopfs_create,
918 .open = ciopfs_open,
919 .read = ciopfs_read,
920 .write = ciopfs_write,
921 .statfs = ciopfs_statfs,
922 .flush = ciopfs_flush,
923 .release = ciopfs_release,
924 .fsync = ciopfs_fsync,
925 .access = ciopfs_access,
926 .setxattr = ciopfs_setxattr,
927 .getxattr = ciopfs_getxattr,
928 .listxattr = ciopfs_listxattr,
929 .removexattr = ciopfs_removexattr,
930 .lock = ciopfs_lock,
931 .init = ciopfs_init
934 static void usage(const char *name)
936 fprintf(stderr, "usage: %s directory mountpoint [options]\n"
937 "\n"
938 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
939 "\n"
940 "general options:\n"
941 " -o opt,[opt...] mount options\n"
942 " -h|--help print help\n"
943 " --version print version\n"
944 "\n", name);
948 enum {
949 CIOPFS_OPT_HELP,
950 CIOPFS_OPT_VERSION
953 static int ciopfs_opt_parse(void *data, const char *arg, int key, struct fuse_args *outargs)
955 switch (key) {
956 case FUSE_OPT_KEY_NONOPT:
957 if (!dirname) {
958 /* realpath(char *s, NULL) is a POSIX.1-2008 extension, originally from GLIBC,
959 and might be unavailible on older non-glibc systems. */
960 if (!(dirname = realpath(arg, NULL))) {
961 perror(outargs->argv[0]);
962 exit(1);
964 return 0;
966 return 1;
967 case FUSE_OPT_KEY_OPT:
968 if (arg[0] == '-') {
969 switch (arg[1]) {
970 case 'd':
971 case 'f':
972 dolog = stderr_print;
974 } else if (!strcmp("allow_other", arg)) {
975 /* disable multithreaded mode if the file system
976 * is accessible to multiple users simultanousely
977 * because we can't store uid/gid per thread and
978 * this leads to all sorts of race conditions and
979 * security issues.
981 single_threaded = (getuid() == 0);
983 return 1;
984 case CIOPFS_OPT_HELP:
985 usage(outargs->argv[0]);
986 fuse_opt_add_arg(outargs, "-ho");
987 fuse_main(outargs->argc, outargs->argv, &ciopfs_operations, NULL);
988 exit(0);
989 case CIOPFS_OPT_VERSION:
990 fprintf(stderr, "%s: "VERSION" fuse: %d\n", outargs->argv[0], fuse_version());
991 exit(0);
992 default:
993 fprintf(stderr, "see `%s -h' for usage\n", outargs->argv[0]);
994 exit(1);
996 return 1;
999 static struct fuse_opt ciopfs_opts[] = {
1000 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP),
1001 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP),
1002 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION),
1003 FUSE_OPT_END
1006 int main(int argc, char *argv[])
1008 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
1009 fuse_opt_parse(&args, &dirname, ciopfs_opts, ciopfs_opt_parse);
1011 if (single_threaded) {
1012 fuse_opt_add_arg(&args, "-s");
1013 log_print("disabling multithreaded mode for root mounted "
1014 "filesystem that is accessible for other users "
1015 "via the `-o allow_other' option\n");
1018 umask(0);
1019 return fuse_main(args.argc, args.argv, &ciopfs_operations, NULL);