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)) {
327 debug("%s\n", strerror(errno
));
333 static int ciopfs_set_orig_name_path(const char *path
, const char *origpath
)
335 char *filename
= strrchr(origpath
, '/');
337 filename
= (char *)origpath
;
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
));
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
))
360 enter_user_context_effective();
361 int res
= lstat(p
, st_data
);
362 leave_user_context_effective();
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();
378 static int ciopfs_readlink(const char *path
, char *buf
, size_t size
)
380 char *p
= map_path(path
);
381 if (unlikely(p
== NULL
))
383 enter_user_context_effective();
384 int res
= readlink(p
, buf
, size
- 1);
385 leave_user_context_effective();
393 static int ciopfs_opendir(const char *path
, struct fuse_file_info
*fi
)
395 char *p
= map_path(path
);
396 if (unlikely(p
== NULL
))
398 enter_user_context_effective();
399 DIR *dp
= opendir(p
);
400 leave_user_context_effective();
406 fi
->fh
= (uint64_t)(uintptr_t)dp
;
410 static int ciopfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
411 off_t offset
, struct fuse_file_info
*fi
)
414 DIR *dp
= (DIR *)(uintptr_t)fi
->fh
;
416 char *p
= map_path(path
);
417 if (unlikely(p
== NULL
))
419 size_t pathlen
= strlen(p
);
420 char dnamebuf
[PATH_MAX
];
421 char attrbuf
[FILENAME_MAX
];
423 if (pathlen
> PATH_MAX
) {
435 while ((de
= readdir(dp
)) != NULL
) {
440 /* skip any entry which is not all lower case for now */
441 if (str_contains_upper(de
->d_name
))
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
))
451 /* check whether there is an original name associated with
452 * this path and if so return it instead of the all lower
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
))
466 ciopfs_remove_orig_name(dnamebuf
);
472 debug("dname: %s\n", dname
);
473 if (filler(buf
, dname
, &st
, telldir(dp
)))
482 static int ciopfs_releasedir(const char *path
, struct fuse_file_info
*fi
)
485 closedir((DIR *)(uintptr_t)fi
->fh
);
489 static int ciopfs_mknod(const char *path
, mode_t mode
, dev_t rdev
)
492 char *p
= map_path(path
);
493 if (unlikely(p
== NULL
))
495 enter_user_context_effective();
496 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
499 res
= open(p
, O_CREAT
| O_EXCL
| O_WRONLY
, mode
);
501 ciopfs_set_orig_name_fd(res
, path
);
504 } else if (S_ISFIFO(mode
)) {
505 res
= mkfifo(p
, mode
);
507 res
= mknod(p
, mode
, rdev
);
508 leave_user_context_effective();
516 static int ciopfs_mkdir(const char *path
, mode_t mode
)
519 char *p
= map_path(path
);
520 if (unlikely(p
== NULL
))
522 enter_user_context_effective();
523 int res
= mkdir(p
, mode
);
524 leave_user_context_effective();
531 ciopfs_set_orig_name_path(p
, path
);
537 static int ciopfs_unlink(const char *path
)
539 char *p
= map_path(path
);
540 if (unlikely(p
== NULL
))
542 enter_user_context_effective();
544 leave_user_context_effective();
551 static int ciopfs_rmdir(const char *path
)
553 char *p
= map_path(path
);
554 if (unlikely(p
== NULL
))
556 enter_user_context_effective();
558 leave_user_context_effective();
565 static int ciopfs_symlink(const char *from
, const char *to
)
568 char *f
= map_path(from
);
569 char *t
= map_path(to
);
570 if (unlikely(f
== NULL
|| t
== NULL
))
572 enter_user_context_effective();
573 int res
= symlink(f
, t
);
574 leave_user_context_effective();
579 ciopfs_set_orig_name_path(t
, to
);
586 static int ciopfs_rename(const char *from
, const char *to
)
589 char *f
= map_path(from
);
590 char *t
= map_path(to
);
591 if (unlikely(f
== NULL
|| t
== NULL
))
593 enter_user_context_effective();
594 int res
= rename(f
, t
);
595 leave_user_context_effective();
600 ciopfs_set_orig_name_path(t
, to
);
607 static int ciopfs_link(const char *from
, const char *to
)
610 char *f
= map_path(from
);
611 char *t
= map_path(to
);
612 if (unlikely(f
== NULL
|| t
== NULL
))
614 enter_user_context_effective();
615 int res
= link(f
, t
);
616 leave_user_context_effective();
621 ciopfs_set_orig_name_path(t
, to
);
628 static int ciopfs_chmod(const char *path
, mode_t mode
)
630 char *p
= map_path(path
);
631 if (unlikely(p
== NULL
))
633 enter_user_context_effective();
634 int res
= chmod(p
, mode
);
635 leave_user_context_effective();
642 static int ciopfs_chown(const char *path
, uid_t uid
, gid_t gid
)
644 char *p
= map_path(path
);
645 if (unlikely(p
== NULL
))
647 enter_user_context_effective();
648 int res
= lchown(p
, uid
, gid
);
649 leave_user_context_effective();
656 static int ciopfs_truncate(const char *path
, off_t size
)
658 char *p
= map_path(path
);
659 if (unlikely(p
== NULL
))
661 enter_user_context_effective();
662 int res
= truncate(p
, size
);
663 leave_user_context_effective();
670 static int ciopfs_ftruncate(const char *path
, off_t size
, struct fuse_file_info
*fi
)
672 enter_user_context_effective();
673 int res
= ftruncate(fi
->fh
, size
);
674 leave_user_context_effective();
681 static int ciopfs_utimens(const char *path
, const struct timespec ts
[2])
683 char *p
= map_path(path
);
684 if (unlikely(p
== NULL
))
686 struct timeval tv
[2];
688 tv
[0].tv_sec
= ts
[0].tv_sec
;
689 tv
[0].tv_usec
= ts
[0].tv_nsec
/ 1000;
690 tv
[1].tv_sec
= ts
[1].tv_sec
;
691 tv
[1].tv_usec
= ts
[1].tv_nsec
/ 1000;
693 enter_user_context_effective();
694 int res
= utimes(p
, tv
);
695 leave_user_context_effective();
702 static int ciopfs_create(const char *path
, mode_t mode
, struct fuse_file_info
*fi
)
704 char *p
= map_path(path
);
705 if (unlikely(p
== NULL
))
707 enter_user_context_effective();
708 int fd
= open(p
, fi
->flags
, mode
);
709 leave_user_context_effective();
713 ciopfs_set_orig_name_fd(fd
, path
);
718 static int ciopfs_open(const char *path
, struct fuse_file_info
*fi
)
720 char *p
= map_path(path
);
721 if (unlikely(p
== NULL
))
723 enter_user_context_effective();
724 int fd
= open(p
, fi
->flags
);
725 leave_user_context_effective();
729 if (fi
->flags
& O_CREAT
)
730 ciopfs_set_orig_name_fd(fd
, path
);
735 static int ciopfs_read(const char *path
, char *buf
, size_t size
, off_t offset
,
736 struct fuse_file_info
*fi
)
738 int res
= pread(fi
->fh
, buf
, size
, offset
);
744 static int ciopfs_write(const char *path
, const char *buf
, size_t size
,
745 off_t offset
, struct fuse_file_info
*fi
)
747 int res
= pwrite(fi
->fh
, buf
, size
, offset
);
753 static int ciopfs_statfs(const char *path
, struct statvfs
*stbuf
)
755 char *p
= map_path(path
);
756 if (unlikely(p
== NULL
))
758 enter_user_context_effective();
759 int res
= statvfs(p
, stbuf
);
760 leave_user_context_effective();
768 static int ciopfs_flush(const char *path
, struct fuse_file_info
*fi
)
770 /* This is called from every close on an open file, so call the
771 close on the underlying filesystem. But since flush may be
772 called multiple times for an open file, this must not really
773 close the file. This is important if used on a network
774 filesystem like NFS which flush the data/metadata on close() */
775 int res
= close(dup(fi
->fh
));
782 static int ciopfs_release(const char *path
, struct fuse_file_info
*fi
)
788 static int ciopfs_fsync(const char *path
, int isdatasync
, struct fuse_file_info
*fi
)
791 #ifdef HAVE_FDATASYNC
793 res
= fdatasync(fi
->fh
);
802 static int ciopfs_access(const char *path
, int mode
)
804 char *p
= map_path(path
);
805 if (unlikely(p
== NULL
))
807 enter_user_context_real();
808 int res
= access(p
, mode
);
809 leave_user_context_real();
816 static int ciopfs_setxattr(const char *path
, const char *name
, const char *value
,
817 size_t size
, int flags
)
819 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
820 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
823 char *p
= map_path(path
);
824 if (unlikely(p
== NULL
))
826 enter_user_context_effective();
827 int res
= lsetxattr(p
, name
, value
, size
, flags
);
828 leave_user_context_effective();
835 static int ciopfs_getxattr(const char *path
, const char *name
, char *value
, size_t size
)
837 char *p
= map_path(path
);
838 if (unlikely(p
== NULL
))
840 enter_user_context_effective();
841 int res
= lgetxattr(p
, name
, value
, size
);
842 leave_user_context_effective();
849 static int ciopfs_listxattr(const char *path
, char *list
, size_t size
)
851 char *p
= map_path(path
);
852 if (unlikely(p
== NULL
))
854 enter_user_context_effective();
855 int res
= llistxattr(p
, list
, size
);
856 leave_user_context_effective();
863 static int ciopfs_removexattr(const char *path
, const char *name
)
865 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
866 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
869 char *p
= map_path(path
);
870 if (unlikely(p
== NULL
))
872 enter_user_context_effective();
873 int res
= lremovexattr(p
, name
);
874 leave_user_context_effective();
881 static int ciopfs_lock(const char *path
, struct fuse_file_info
*fi
, int cmd
,
884 return ulockmgr_op(fi
->fh
, cmd
, lock
, &fi
->lock_owner
,
885 sizeof(fi
->lock_owner
));
888 static void *ciopfs_init(struct fuse_conn_info
*conn
)
890 if (chdir(dirname
) == -1) {
891 log_print("init: %s\n", strerror(errno
));
897 struct fuse_operations ciopfs_operations
= {
898 .getattr
= ciopfs_getattr
,
899 .fgetattr
= ciopfs_fgetattr
,
900 .readlink
= ciopfs_readlink
,
901 .opendir
= ciopfs_opendir
,
902 .readdir
= ciopfs_readdir
,
903 .releasedir
= ciopfs_releasedir
,
904 .mknod
= ciopfs_mknod
,
905 .mkdir
= ciopfs_mkdir
,
906 .symlink
= ciopfs_symlink
,
907 .unlink
= ciopfs_unlink
,
908 .rmdir
= ciopfs_rmdir
,
909 .rename
= ciopfs_rename
,
911 .chmod
= ciopfs_chmod
,
912 .chown
= ciopfs_chown
,
913 .truncate
= ciopfs_truncate
,
914 .ftruncate
= ciopfs_ftruncate
,
915 .utimens
= ciopfs_utimens
,
916 .create
= ciopfs_create
,
919 .write
= ciopfs_write
,
920 .statfs
= ciopfs_statfs
,
921 .flush
= ciopfs_flush
,
922 .release
= ciopfs_release
,
923 .fsync
= ciopfs_fsync
,
924 .access
= ciopfs_access
,
925 .setxattr
= ciopfs_setxattr
,
926 .getxattr
= ciopfs_getxattr
,
927 .listxattr
= ciopfs_listxattr
,
928 .removexattr
= ciopfs_removexattr
,
933 static void usage(const char *name
)
935 fprintf(stderr
, "usage: %s directory mountpoint [options]\n"
937 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
940 " -o opt,[opt...] mount options\n"
941 " -h|--help print help\n"
942 " --version print version\n"
952 static int ciopfs_opt_parse(void *data
, const char *arg
, int key
, struct fuse_args
*outargs
)
955 case FUSE_OPT_KEY_NONOPT
:
957 /* XXX: realpath(char *s, NULL) is a glibc extension */
958 if (!(dirname
= realpath(arg
, NULL
))) {
959 perror(outargs
->argv
[0]);
965 case FUSE_OPT_KEY_OPT
:
970 dolog
= stderr_print
;
972 } else if (!strcmp("allow_other", arg
)) {
973 /* disable multithreaded mode if the file system
974 * is accessible to multiple users simultanousely
975 * because we can't store uid/gid per thread and
976 * this leads to all sorts of race conditions and
979 single_threaded
= (getuid() == 0);
982 case CIOPFS_OPT_HELP
:
983 usage(outargs
->argv
[0]);
984 fuse_opt_add_arg(outargs
, "-ho");
985 fuse_main(outargs
->argc
, outargs
->argv
, &ciopfs_operations
, NULL
);
987 case CIOPFS_OPT_VERSION
:
988 fprintf(stderr
, "%s: "VERSION
" fuse: %d\n", outargs
->argv
[0], fuse_version());
991 fprintf(stderr
, "see `%s -h' for usage\n", outargs
->argv
[0]);
997 static struct fuse_opt ciopfs_opts
[] = {
998 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP
),
999 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP
),
1000 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION
),
1004 int main(int argc
, char *argv
[])
1006 struct fuse_args args
= FUSE_ARGS_INIT(argc
, argv
);
1007 fuse_opt_parse(&args
, &dirname
, ciopfs_opts
, ciopfs_opt_parse
);
1009 if (single_threaded
) {
1010 fuse_opt_add_arg(&args
, "-s");
1011 log_print("disabling multithreaded mode for root mounted "
1012 "filesystem that is accessible for other users "
1013 "via the `-o allow_other' option\n");
1017 return fuse_main(args
.argc
, args
.argv
, &ciopfs_operations
, NULL
);