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>
71 #include <unicode/ustring.h>
72 #include <unicode/uchar.h>
75 #define log_print(format, args...) (*dolog)(format, ## args)
78 # define debug(format, args...)
80 # define debug log_print
83 #define CIOPFS_ATTR_NAME "user.filename"
90 #define FILENAME_MAX 4096
94 static const char *dirname
;
96 void stderr_print(const char *fmt
, ...)
100 fputs("ciopfs: ", stderr
);
101 vfprintf(stderr
, fmt
, ap
);
105 void syslog_print(const char *fmt
, ...)
109 vsyslog(LOG_NOTICE
, fmt
, ap
);
113 static void (*dolog
)(const char *fmt
, ...) = syslog_print
;
117 static inline UChar
*utf8_to_utf16(const char *str
, int32_t *length
)
120 UErrorCode status
= U_ZERO_ERROR
;
122 u_strFromUTF8(NULL
, 0, length
, str
, -1, &status
);
123 status
= U_ZERO_ERROR
;
124 (*length
)++; /* for the NUL char */
125 ustr
= malloc(sizeof(UChar
) * (*length
));
128 u_strFromUTF8(ustr
, *length
, NULL
, str
, -1, &status
);
129 if (U_FAILURE(status
)) {
136 static inline char *utf16_to_utf8(UChar
*ustr
, int32_t *length
)
139 UErrorCode status
= U_ZERO_ERROR
;
141 u_strToUTF8(NULL
, 0, length
, ustr
, -1, &status
);
142 status
= U_ZERO_ERROR
;
143 (*length
)++; /* for the NUL char */
144 str
= malloc(*length
);
147 u_strToUTF8(str
, *length
, NULL
, ustr
, -1, &status
);
148 if (U_FAILURE(status
)) {
155 static inline char *utf_fold(const char *s
)
160 UErrorCode status
= U_ZERO_ERROR
;
162 ustr
= utf8_to_utf16(s
, &length
);
165 u_strFoldCase(ustr
, length
, ustr
, length
, U_FOLD_CASE_EXCLUDE_SPECIAL_I
, &status
);
166 if (U_FAILURE(status
))
168 str
= utf16_to_utf8(ustr
, &length
);
173 static inline bool utf_contains_upper(const char *s
)
178 UChar
*ustr
= utf8_to_utf16(s
, &length
);
181 for (i
= 0; i
< length
; /* U16_NEXT post-increments */) {
182 U16_NEXT(ustr
, i
, length
, c
);
193 #endif /* HAVE_LIBICUUC */
195 static inline bool str_contains_upper(const char *s
)
198 return utf_contains_upper(s
);
208 static inline char *str_fold(const char *src
)
211 return utf_fold(src
);
214 char *dest
= malloc(strlen(src
));
217 for (t
= dest
; *src
; src
++, t
++)
224 static char *map_path(const char *path
)
227 // XXX: malloc failure, memory fragmentation?
228 if (path
[0] == '/') {
235 debug("%s => %s\n", path
, p
);
239 /* Returns the supplementary group IDs of a calling process which
240 * isued the file system operation.
242 * As indicated by Miklos Szeredi the group list is available in
244 * /proc/$PID/task/$TID/status
246 * and fuse supplies TID in get_fuse_context()->pid.
248 * Jean-Pierre Andre found out that the same information is also
251 * /proc/$TID/task/$TID/status
253 * which is used in this implementation.
256 static size_t get_groups(gid_t
**groups
)
258 static char key
[] = "\nGroups:\t";
259 char filename
[64], buf
[2048], *s
, *t
, c
= '\0';
260 int fd
, num_read
, matched
= 0;
262 gid_t
*gids
, grp
= 0;
263 pid_t tid
= fuse_get_context()->pid
;
265 sprintf(filename
, "/proc/%u/task/%u/status", tid
, tid
);
266 fd
= open(filename
, O_RDONLY
);
272 num_read
= read(fd
, buf
, sizeof(buf
) - 1);
277 buf
[num_read
] = '\0';
283 if (key
[matched
] == c
) {
288 matched
= (key
[0] == c
);
303 *groups
= gids
= malloc(n
* sizeof(gid_t
));
308 while ((c
= *s
++) != '\n') {
309 if (c
>= '0' && c
<= '9')
310 grp
= grp
*10 + c
- '0';
320 /* This only works when the fs is mounted by root.
321 * Further more it relies on the fact that the euid
322 * and egid are stored per thread.
325 static inline void enter_user_context()
329 struct fuse_context
*c
= fuse_get_context();
331 if (getuid() || c
->uid
== 0)
333 if ((ngroups
= get_groups(&groups
))) {
334 setgroups(ngroups
, groups
);
341 static inline void leave_user_context()
350 static ssize_t
ciopfs_get_orig_name(const char *path
, char *value
, size_t size
)
353 debug("looking up original file name of %s ", path
);
354 attrlen
= lgetxattr(path
, CIOPFS_ATTR_NAME
, value
, size
);
356 value
[attrlen
] = '\0';
357 debug("found %s\n", value
);
359 debug("nothing found\n");
364 static int ciopfs_set_orig_name_fd(int fd
, const char *origpath
)
366 char *filename
= strrchr(origpath
, '/');
368 filename
= (char *)origpath
;
371 //XXX: map_path memory leak
372 debug("storing original name '%s' in '%s'\n", filename
, map_path(origpath
));
373 if (fsetxattr(fd
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
374 debug("%s\n", strerror(errno
));
380 static int ciopfs_set_orig_name_path(const char *path
, const char *origpath
)
382 char *filename
= strrchr(origpath
, '/');
384 filename
= (char *)origpath
;
387 debug("storing original name '%s' in '%s'\n", filename
, path
);
388 // XXX: setting an extended attribute on a symlink doesn't seem to work (EPERM)
389 if (lsetxattr(path
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
390 debug("%s\n", strerror(errno
));
396 static int ciopfs_remove_orig_name(const char *path
)
398 debug("removing original file name of %s\n", path
);
399 return lremovexattr(path
, CIOPFS_ATTR_NAME
);
402 static int ciopfs_getattr(const char *path
, struct stat
*st_data
)
404 char *p
= map_path(path
);
405 int res
= lstat(p
, st_data
);
407 return (res
== -1) ? -errno
: 0;
410 static int ciopfs_fgetattr(const char *path
, struct stat
*stbuf
,
411 struct fuse_file_info
*fi
)
413 int res
= fstat(fi
->fh
, stbuf
);
420 static int ciopfs_readlink(const char *path
, char *buf
, size_t size
)
422 char *p
= map_path(path
);
423 enter_user_context();
424 int res
= readlink(p
, buf
, size
- 1);
425 leave_user_context();
433 static int ciopfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
434 off_t offset
, struct fuse_file_info
*fi
)
439 char *p
= map_path(path
);
440 size_t pathlen
= strlen(p
);
441 char dnamebuf
[PATH_MAX
];
442 char attrbuf
[FILENAME_MAX
];
444 if (pathlen
> PATH_MAX
) {
460 while ((de
= readdir(dp
)) != NULL
) {
465 /* skip any entry which is not all lower case for now */
466 if (str_contains_upper(de
->d_name
))
469 memset(&st
, 0, sizeof(st
));
470 st
.st_ino
= de
->d_ino
;
471 st
.st_mode
= de
->d_type
<< 12;
473 if (!strcmp(".", de
->d_name
) || !strcmp("..", de
->d_name
))
476 /* check whether there is an original name associated with
477 * this path and if so return it instead of the all lower
480 snprintf(dnamebuf
, sizeof dnamebuf
, "%s/%s", p
, de
->d_name
);
481 debug("dnamebuf: %s de->d_name: %s\n", dnamebuf
, de
->d_name
);
482 if (ciopfs_get_orig_name(dnamebuf
, attrbuf
, sizeof attrbuf
) > 0) {
483 /* we found an original name now check whether it is
484 * still accurate and if not remove it
486 attrlower
= str_fold(attrbuf
);
487 if (attrlower
&& !strcmp(attrlower
, de
->d_name
))
491 ciopfs_remove_orig_name(dnamebuf
);
497 debug("dname: %s\n", dname
);
498 if (filler(buf
, dname
, &st
, 0))
508 static int ciopfs_mknod(const char *path
, mode_t mode
, dev_t rdev
)
511 char *p
= map_path(path
);
512 enter_user_context();
513 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
516 res
= open(p
, O_CREAT
| O_EXCL
| O_WRONLY
, mode
);
518 ciopfs_set_orig_name_fd(res
, path
);
521 } else if (S_ISFIFO(mode
)) {
522 res
= mkfifo(p
, mode
);
524 res
= mknod(p
, mode
, rdev
);
525 leave_user_context();
533 static int ciopfs_mkdir(const char *path
, mode_t mode
)
536 char *p
= map_path(path
);
537 enter_user_context();
538 int res
= mkdir(p
, mode
);
539 leave_user_context();
546 ciopfs_set_orig_name_path(p
, path
);
552 static int ciopfs_unlink(const char *path
)
554 char *p
= map_path(path
);
555 enter_user_context();
557 leave_user_context();
564 static int ciopfs_rmdir(const char *path
)
566 char *p
= map_path(path
);
567 enter_user_context();
569 leave_user_context();
576 static int ciopfs_symlink(const char *from
, const char *to
)
579 char *f
= map_path(from
);
580 char *t
= map_path(to
);
581 enter_user_context();
582 int res
= symlink(f
, t
);
583 leave_user_context();
588 ciopfs_set_orig_name_path(t
, to
);
595 static int ciopfs_rename(const char *from
, const char *to
)
598 char *f
= map_path(from
);
599 char *t
= map_path(to
);
600 enter_user_context();
601 int res
= rename(f
, t
);
602 leave_user_context();
607 ciopfs_set_orig_name_path(t
, to
);
614 static int ciopfs_link(const char *from
, const char *to
)
617 char *f
= map_path(from
);
618 char *t
= map_path(to
);
619 enter_user_context();
620 int res
= link(f
, t
);
621 leave_user_context();
626 ciopfs_set_orig_name_path(t
, to
);
633 static int ciopfs_chmod(const char *path
, mode_t mode
)
635 char *p
= map_path(path
);
636 enter_user_context();
637 int res
= chmod(p
, mode
);
638 leave_user_context();
645 static int ciopfs_chown(const char *path
, uid_t uid
, gid_t gid
)
647 char *p
= map_path(path
);
648 enter_user_context();
649 int res
= lchown(p
, uid
, gid
);
650 leave_user_context();
657 static int ciopfs_truncate(const char *path
, off_t size
)
659 char *p
= map_path(path
);
660 enter_user_context();
661 int res
= truncate(p
, size
);
662 leave_user_context();
669 static int ciopfs_ftruncate(const char *path
, off_t size
, struct fuse_file_info
*fi
)
671 enter_user_context();
672 int res
= ftruncate(fi
->fh
, size
);
673 leave_user_context();
680 static int ciopfs_utimens(const char *path
, const struct timespec ts
[2])
682 char *p
= map_path(path
);
683 struct timeval tv
[2];
685 tv
[0].tv_sec
= ts
[0].tv_sec
;
686 tv
[0].tv_usec
= ts
[0].tv_nsec
/ 1000;
687 tv
[1].tv_sec
= ts
[1].tv_sec
;
688 tv
[1].tv_usec
= ts
[1].tv_nsec
/ 1000;
690 enter_user_context();
691 int res
= utimes(p
, tv
);
692 leave_user_context();
699 static int ciopfs_create(const char *path
, mode_t mode
, struct fuse_file_info
*fi
)
701 char *p
= map_path(path
);
702 enter_user_context();
703 int fd
= open(p
, fi
->flags
, mode
);
704 leave_user_context();
708 ciopfs_set_orig_name_fd(fd
, path
);
713 static int ciopfs_open(const char *path
, struct fuse_file_info
*fi
)
715 char *p
= map_path(path
);
716 enter_user_context();
717 int fd
= open(p
, fi
->flags
);
718 leave_user_context();
722 if (fi
->flags
& O_CREAT
)
723 ciopfs_set_orig_name_fd(fd
, path
);
728 static int ciopfs_read(const char *path
, char *buf
, size_t size
, off_t offset
,
729 struct fuse_file_info
*fi
)
731 int res
= pread(fi
->fh
, buf
, size
, offset
);
737 static int ciopfs_write(const char *path
, const char *buf
, size_t size
,
738 off_t offset
, struct fuse_file_info
*fi
)
740 int res
= pwrite(fi
->fh
, buf
, size
, offset
);
746 static int ciopfs_statfs(const char *path
, struct statvfs
*stbuf
)
748 char *p
= map_path(path
);
749 enter_user_context();
750 int res
= statvfs(p
, stbuf
);
751 leave_user_context();
759 static int ciopfs_flush(const char *path
, struct fuse_file_info
*fi
)
761 /* This is called from every close on an open file, so call the
762 close on the underlying filesystem. But since flush may be
763 called multiple times for an open file, this must not really
764 close the file. This is important if used on a network
765 filesystem like NFS which flush the data/metadata on close() */
766 int res
= close(dup(fi
->fh
));
773 static int ciopfs_release(const char *path
, struct fuse_file_info
*fi
)
779 static int ciopfs_fsync(const char *path
, int isdatasync
, struct fuse_file_info
*fi
)
782 #ifdef HAVE_FDATASYNC
784 res
= fdatasync(fi
->fh
);
793 static int ciopfs_access(const char *path
, int mode
)
795 char *p
= map_path(path
);
796 enter_user_context();
797 int res
= access(p
, mode
);
798 leave_user_context();
805 static int ciopfs_setxattr(const char *path
, const char *name
, const char *value
,
806 size_t size
, int flags
)
808 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
809 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
812 char *p
= map_path(path
);
813 enter_user_context();
814 int res
= lsetxattr(p
, name
, value
, size
, flags
);
815 leave_user_context();
822 static int ciopfs_getxattr(const char *path
, const char *name
, char *value
, size_t size
)
824 char *p
= map_path(path
);
825 enter_user_context();
826 int res
= lgetxattr(p
, name
, value
, size
);
827 leave_user_context();
834 static int ciopfs_listxattr(const char *path
, char *list
, size_t size
)
836 char *p
= map_path(path
);
837 enter_user_context();
838 int res
= llistxattr(p
, list
, size
);
839 leave_user_context();
846 static int ciopfs_removexattr(const char *path
, const char *name
)
848 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
849 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
852 char *p
= map_path(path
);
853 enter_user_context();
854 int res
= lremovexattr(p
, name
);
855 leave_user_context();
862 static int ciopfs_lock(const char *path
, struct fuse_file_info
*fi
, int cmd
,
865 return ulockmgr_op(fi
->fh
, cmd
, lock
, &fi
->lock_owner
,
866 sizeof(fi
->lock_owner
));
869 static void *ciopfs_init(struct fuse_conn_info
*conn
)
871 if (chdir(dirname
) == -1) {
872 log_print("init: %s\n", strerror(errno
));
878 struct fuse_operations ciopfs_operations
= {
879 .getattr
= ciopfs_getattr
,
880 .fgetattr
= ciopfs_fgetattr
,
881 .readlink
= ciopfs_readlink
,
882 .readdir
= ciopfs_readdir
,
883 .mknod
= ciopfs_mknod
,
884 .mkdir
= ciopfs_mkdir
,
885 .symlink
= ciopfs_symlink
,
886 .unlink
= ciopfs_unlink
,
887 .rmdir
= ciopfs_rmdir
,
888 .rename
= ciopfs_rename
,
890 .chmod
= ciopfs_chmod
,
891 .chown
= ciopfs_chown
,
892 .truncate
= ciopfs_truncate
,
893 .ftruncate
= ciopfs_ftruncate
,
894 .utimens
= ciopfs_utimens
,
895 .create
= ciopfs_create
,
898 .write
= ciopfs_write
,
899 .statfs
= ciopfs_statfs
,
900 .flush
= ciopfs_flush
,
901 .release
= ciopfs_release
,
902 .fsync
= ciopfs_fsync
,
903 .access
= ciopfs_access
,
904 .setxattr
= ciopfs_setxattr
,
905 .getxattr
= ciopfs_getxattr
,
906 .listxattr
= ciopfs_listxattr
,
907 .removexattr
= ciopfs_removexattr
,
918 static void usage(const char *name
)
920 fprintf(stderr
, "usage: %s directory mountpoint [options]\n"
922 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
925 " -o opt,[opt...] mount options\n"
926 " -h|--help print help\n"
927 " --version print version\n"
937 static int ciopfs_opt_parse(void *data
, const char *arg
, int key
, struct fuse_args
*outargs
)
940 case FUSE_OPT_KEY_NONOPT
:
942 /* XXX: realpath(char *s, NULL) is a glibc extension */
943 if (!(dirname
= realpath(arg
, NULL
))) {
944 perror(outargs
->argv
[0]);
950 case FUSE_OPT_KEY_OPT
:
955 dolog
= stderr_print
;
959 case CIOPFS_OPT_HELP
:
960 usage(outargs
->argv
[0]);
961 fuse_opt_add_arg(outargs
, "-ho");
962 fuse_main(outargs
->argc
, outargs
->argv
, &ciopfs_operations
, NULL
);
964 case CIOPFS_OPT_VERSION
:
965 fprintf(stderr
, "%s: "VERSION
" fuse: %d\n", outargs
->argv
[0], fuse_version());
968 fprintf(stderr
, "see `%s -h' for usage\n", outargs
->argv
[0]);
974 static struct fuse_opt ciopfs_opts
[] = {
975 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP
),
976 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP
),
977 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION
),
981 int main(int argc
, char *argv
[])
983 struct fuse_args args
= FUSE_ARGS_INIT(argc
, argv
);
984 if (fuse_opt_parse(&args
, &dirname
, ciopfs_opts
, ciopfs_opt_parse
)) {
985 fprintf(stderr
, "Invalid arguments, see `%s -h' for usage\n", argv
[0]);
989 return fuse_main(args
.argc
, args
.argv
, &ciopfs_operations
, NULL
);