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.
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
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.
42 * ciopfs directory mountpoint [options]
47 #define _XOPEN_SOURCE 500 /* For pread()/pwrite() */
50 #define _BSD_SOURCE /* for vsyslog() */
54 #include <sys/xattr.h>
70 # define likely(x) __builtin_expect(!!(x), 1)
71 # define unlikely(x) __builtin_expect(!!(x), 0)
73 # define likely(x) (x)
74 # define unlikely(x) (x)
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);
83 # include "unicode-glib.c"
84 #elif defined HAVE_LIBICUUC
85 # include "unicode-icu.c"
90 #define log_print(format, args...) (*dolog)(format, ## args)
93 # define debug(format, args...)
95 # define debug log_print
98 #define CIOPFS_ATTR_NAME "user.filename"
101 #define PATH_MAX 4096
105 #define FILENAME_MAX 4096
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
, ...)
119 fputs("ciopfs: ", stderr
);
120 vfprintf(stderr
, fmt
, ap
);
124 void syslog_print(const char *fmt
, ...)
128 vsyslog(LOG_NOTICE
, fmt
, ap
);
132 static void (*dolog
)(const char *fmt
, ...) = syslog_print
;
134 static char *map_path(const char *path
)
137 /* XXX: memory fragmentation? */
138 if (path
[0] == '/') {
145 debug("%s => %s\n", path
, 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
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;
172 gid_t
*gids
, grp
= 0;
174 sprintf(filename
, "/proc/%u/task/%u/status", pid
, pid
);
175 fd
= open(filename
, O_RDONLY
);
181 num_read
= read(fd
, buf
, sizeof(buf
) - 1);
186 buf
[num_read
] = '\0';
192 if (key
[matched
] == c
) {
197 matched
= (key
[0] == c
);
212 *groups
= gids
= malloc(n
* sizeof(gid_t
));
217 while ((c
= *s
++) != '\n') {
218 if (c
>= '0' && c
<= '9')
219 grp
= grp
*10 + c
- '0';
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
236 static inline void enter_user_context_effective()
240 struct fuse_context
*c
= fuse_get_context();
242 if (!single_threaded
|| getuid())
244 if ((ngroups
= get_groups(c
->pid
, &groups
))) {
245 setgroups(ngroups
, groups
);
253 static inline void leave_user_context_effective()
255 if (!single_threaded
|| getuid())
262 /* access(2) checks the real uid/gid not the effective one
263 * we therefore switch them if run as root and in single
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()
277 struct fuse_context
*c
= fuse_get_context();
279 if (!single_threaded
|| geteuid())
281 if ((ngroups
= get_groups(c
->pid
, &groups
))) {
282 setgroups(ngroups
, groups
);
285 setregid(c
->gid
, -1);
286 setreuid(c
->uid
, -1);
289 static inline void leave_user_context_real()
291 if (!single_threaded
|| geteuid())
298 static ssize_t
ciopfs_get_orig_name(const char *path
, char *value
, size_t size
)
301 debug("looking up original file name of %s ", path
);
302 attrlen
= lgetxattr(path
, CIOPFS_ATTR_NAME
, value
, size
);
304 value
[attrlen
] = '\0';
305 debug("found %s\n", value
);
307 debug("nothing found\n");
312 static int ciopfs_set_orig_name_fd(int fd
, const char *origpath
)
314 char *filename
= strrchr(origpath
, '/');
316 filename
= (char *)origpath
;
320 char *path
= map_path(origpath
);
321 if (likely(path
!= NULL
)) {
322 log_print("storing original name '%s' in '%s'\n", filename
, path
);
326 if (fsetxattr(fd
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
328 debug("%s\n", strerror(errno
));
334 static int ciopfs_set_orig_name_path(const char *path
, const char *origpath
)
336 char *filename
= strrchr(origpath
, '/');
338 filename
= (char *)origpath
;
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)) {
345 debug("%s\n", strerror(errno
));
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
))
362 enter_user_context_effective();
363 int res
= lstat(p
, st_data
);
366 leave_user_context_effective();
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
);
378 leave_user_context_effective();
382 static int ciopfs_readlink(const char *path
, char *buf
, size_t size
)
385 char *p
= map_path(path
);
386 if (unlikely(p
== NULL
))
388 enter_user_context_effective();
389 int res
= readlink(p
, buf
, size
- 1);
392 leave_user_context_effective();
400 static int ciopfs_opendir(const char *path
, struct fuse_file_info
*fi
)
403 char *p
= map_path(path
);
404 if (unlikely(p
== NULL
))
406 enter_user_context_effective();
407 DIR *dp
= opendir(p
);
410 leave_user_context_effective();
414 fi
->fh
= (uint64_t)(uintptr_t)dp
;
418 static int ciopfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
419 off_t offset
, struct fuse_file_info
*fi
)
422 DIR *dp
= (DIR *)(uintptr_t)fi
->fh
;
424 char *p
= map_path(path
);
425 if (unlikely(p
== NULL
))
427 size_t pathlen
= strlen(p
);
428 char dnamebuf
[PATH_MAX
];
429 char attrbuf
[FILENAME_MAX
];
431 if (pathlen
> PATH_MAX
) {
443 while ((de
= readdir(dp
)) != NULL
) {
448 /* skip any entry which is not all lower case for now */
449 if (str_contains_upper(de
->d_name
))
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
))
459 /* check whether there is an original name associated with
460 * this path and if so return it instead of the all lower
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
))
474 ciopfs_remove_orig_name(dnamebuf
);
480 debug("dname: %s\n", dname
);
481 if (filler(buf
, dname
, &st
, telldir(dp
)))
490 static int ciopfs_releasedir(const char *path
, struct fuse_file_info
*fi
)
493 closedir((DIR *)(uintptr_t)fi
->fh
);
497 static int ciopfs_mknod(const char *path
, mode_t mode
, dev_t rdev
)
500 char *p
= map_path(path
);
501 if (unlikely(p
== NULL
))
503 enter_user_context_effective();
504 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
507 res
= open(p
, O_CREAT
| O_EXCL
| O_WRONLY
, mode
);
509 ciopfs_set_orig_name_fd(res
, path
);
512 } else if (S_ISFIFO(mode
)) {
513 res
= mkfifo(p
, mode
);
515 res
= mknod(p
, mode
, rdev
);
519 leave_user_context_effective();
524 static int ciopfs_mkdir(const char *path
, mode_t mode
)
526 char *p
= map_path(path
);
527 if (unlikely(p
== NULL
))
529 enter_user_context_effective();
530 int res
= mkdir(p
, mode
);
533 leave_user_context_effective();
535 ciopfs_set_orig_name_path(p
, path
);
540 static int ciopfs_unlink(const char *path
)
542 char *p
= map_path(path
);
543 if (unlikely(p
== NULL
))
545 enter_user_context_effective();
549 leave_user_context_effective();
554 static int ciopfs_rmdir(const char *path
)
556 char *p
= map_path(path
);
557 if (unlikely(p
== NULL
))
559 enter_user_context_effective();
563 leave_user_context_effective();
568 static int ciopfs_symlink(const char *from
, const char *to
)
570 char *t
= map_path(to
);
571 if (unlikely(t
== NULL
))
573 enter_user_context_effective();
574 int res
= symlink(from
, t
);
577 leave_user_context_effective();
579 ciopfs_set_orig_name_path(t
, to
);
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
))
590 enter_user_context_effective();
591 int res
= rename(f
, t
);
594 leave_user_context_effective();
596 ciopfs_set_orig_name_path(t
, to
);
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
))
608 enter_user_context_effective();
609 int res
= link(f
, t
);
612 leave_user_context_effective();
614 ciopfs_set_orig_name_path(t
, to
);
620 static int ciopfs_chmod(const char *path
, mode_t mode
)
622 char *p
= map_path(path
);
623 if (unlikely(p
== NULL
))
625 enter_user_context_effective();
626 int res
= chmod(p
, mode
);
629 leave_user_context_effective();
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
))
639 enter_user_context_effective();
640 int res
= lchown(p
, uid
, gid
);
643 leave_user_context_effective();
648 static int ciopfs_truncate(const char *path
, off_t size
)
650 char *p
= map_path(path
);
651 if (unlikely(p
== NULL
))
653 enter_user_context_effective();
654 int res
= truncate(p
, size
);
657 leave_user_context_effective();
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
);
668 leave_user_context_effective();
672 static int ciopfs_utimens(const char *path
, const struct timespec ts
[2])
674 char *p
= map_path(path
);
675 if (unlikely(p
== NULL
))
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
);
688 leave_user_context_effective();
693 static int ciopfs_create(const char *path
, mode_t mode
, struct fuse_file_info
*fi
)
696 char *p
= map_path(path
);
697 if (unlikely(p
== NULL
))
699 enter_user_context_effective();
700 int fd
= open(p
, fi
->flags
, mode
);
703 leave_user_context_effective();
707 ciopfs_set_orig_name_fd(fd
, path
);
712 static int ciopfs_open(const char *path
, struct fuse_file_info
*fi
)
715 char *p
= map_path(path
);
716 if (unlikely(p
== NULL
))
718 enter_user_context_effective();
719 int fd
= open(p
, fi
->flags
);
722 leave_user_context_effective();
726 if (fi
->flags
& O_CREAT
)
727 ciopfs_set_orig_name_fd(fd
, path
);
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
);
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
);
750 static int ciopfs_statfs(const char *path
, struct statvfs
*stbuf
)
752 char *p
= map_path(path
);
753 if (unlikely(p
== NULL
))
755 enter_user_context_effective();
756 int res
= statvfs(p
, stbuf
);
759 leave_user_context_effective();
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
));
778 static int ciopfs_release(const char *path
, struct fuse_file_info
*fi
)
784 static int ciopfs_fsync(const char *path
, int isdatasync
, struct fuse_file_info
*fi
)
787 #ifdef HAVE_FDATASYNC
789 res
= fdatasync(fi
->fh
);
798 static int ciopfs_access(const char *path
, int mode
)
800 char *p
= map_path(path
);
801 if (unlikely(p
== NULL
))
803 enter_user_context_real();
804 int res
= access(p
, mode
);
807 leave_user_context_real();
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
);
819 char *p
= map_path(path
);
820 if (unlikely(p
== NULL
))
822 enter_user_context_effective();
823 int res
= lsetxattr(p
, name
, value
, size
, flags
);
826 leave_user_context_effective();
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
))
836 enter_user_context_effective();
837 int res
= lgetxattr(p
, name
, value
, size
);
840 leave_user_context_effective();
845 static int ciopfs_listxattr(const char *path
, char *list
, size_t size
)
847 char *p
= map_path(path
);
848 if (unlikely(p
== NULL
))
850 enter_user_context_effective();
851 int res
= llistxattr(p
, list
, size
);
854 leave_user_context_effective();
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
);
865 char *p
= map_path(path
);
866 if (unlikely(p
== NULL
))
868 enter_user_context_effective();
869 int res
= lremovexattr(p
, name
);
872 leave_user_context_effective();
877 static int ciopfs_lock(const char *path
, struct fuse_file_info
*fi
, int cmd
,
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
));
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");
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
,
912 .chmod
= ciopfs_chmod
,
913 .chown
= ciopfs_chown
,
914 .truncate
= ciopfs_truncate
,
915 .ftruncate
= ciopfs_ftruncate
,
916 .utimens
= ciopfs_utimens
,
917 .create
= ciopfs_create
,
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
,
934 static void usage(const char *name
)
936 fprintf(stderr
, "usage: %s directory mountpoint [options]\n"
938 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
941 " -o opt,[opt...] mount options\n"
942 " -h|--help print help\n"
943 " --version print version\n"
953 static int ciopfs_opt_parse(void *data
, const char *arg
, int key
, struct fuse_args
*outargs
)
956 case FUSE_OPT_KEY_NONOPT
:
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]);
967 case FUSE_OPT_KEY_OPT
:
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
981 single_threaded
= (getuid() == 0);
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
);
989 case CIOPFS_OPT_VERSION
:
990 fprintf(stderr
, "%s: "VERSION
" fuse: %d\n", outargs
->argv
[0], fuse_version());
993 fprintf(stderr
, "see `%s -h' for usage\n", outargs
->argv
[0]);
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
),
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");
1019 return fuse_main(args
.argc
, args
.argv
, &ciopfs_operations
, NULL
);