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
;
110 void stderr_print(const char *fmt
, ...)
114 fputs("ciopfs: ", stderr
);
115 vfprintf(stderr
, fmt
, ap
);
119 void syslog_print(const char *fmt
, ...)
123 vsyslog(LOG_NOTICE
, fmt
, ap
);
127 static void (*dolog
)(const char *fmt
, ...) = syslog_print
;
129 static char *map_path(const char *path
)
132 /* XXX: memory fragmentation? */
133 if (path
[0] == '/') {
140 debug("%s => %s\n", path
, p
);
144 /* Returns the supplementary group IDs of a calling process which
145 * isued the file system operation.
147 * As indicated by Miklos Szeredi the group list is available in
149 * /proc/$PID/task/$TID/status
151 * and fuse supplies TID in get_fuse_context()->pid.
153 * Jean-Pierre Andre found out that the same information is also
156 * /proc/$TID/task/$TID/status
158 * which is used in this implementation.
161 static size_t get_groups(gid_t
**groups
)
163 static char key
[] = "\nGroups:\t";
164 char filename
[64], buf
[2048], *s
, *t
, c
= '\0';
165 int fd
, num_read
, matched
= 0;
167 gid_t
*gids
, grp
= 0;
168 pid_t tid
= fuse_get_context()->pid
;
170 sprintf(filename
, "/proc/%u/task/%u/status", tid
, tid
);
171 fd
= open(filename
, O_RDONLY
);
177 num_read
= read(fd
, buf
, sizeof(buf
) - 1);
182 buf
[num_read
] = '\0';
188 if (key
[matched
] == c
) {
193 matched
= (key
[0] == c
);
208 *groups
= gids
= malloc(n
* sizeof(gid_t
));
213 while ((c
= *s
++) != '\n') {
214 if (c
>= '0' && c
<= '9')
215 grp
= grp
*10 + c
- '0';
225 /* This only works when the fs is mounted by root.
226 * Further more it relies on the fact that the euid
227 * and egid are stored per thread.
230 static inline void enter_user_context()
234 struct fuse_context
*c
= fuse_get_context();
236 if (getuid() || c
->uid
== 0)
238 if ((ngroups
= get_groups(&groups
))) {
239 setgroups(ngroups
, groups
);
246 static inline void leave_user_context()
255 static ssize_t
ciopfs_get_orig_name(const char *path
, char *value
, size_t size
)
258 debug("looking up original file name of %s ", path
);
259 attrlen
= lgetxattr(path
, CIOPFS_ATTR_NAME
, value
, size
);
261 value
[attrlen
] = '\0';
262 debug("found %s\n", value
);
264 debug("nothing found\n");
269 static int ciopfs_set_orig_name_fd(int fd
, const char *origpath
)
271 char *filename
= strrchr(origpath
, '/');
273 filename
= (char *)origpath
;
277 char *path
= map_path(origpath
);
278 if (likely(path
!= NULL
)) {
279 log_print("storing original name '%s' in '%s'\n", filename
, path
);
283 if (fsetxattr(fd
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
284 debug("%s\n", strerror(errno
));
290 static int ciopfs_set_orig_name_path(const char *path
, const char *origpath
)
292 char *filename
= strrchr(origpath
, '/');
294 filename
= (char *)origpath
;
297 debug("storing original name '%s' in '%s'\n", filename
, path
);
298 /* XXX: setting an extended attribute on a symlink doesn't seem to work (EPERM) */
299 if (lsetxattr(path
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
300 debug("%s\n", strerror(errno
));
306 static int ciopfs_remove_orig_name(const char *path
)
308 debug("removing original file name of %s\n", path
);
309 return lremovexattr(path
, CIOPFS_ATTR_NAME
);
312 static int ciopfs_getattr(const char *path
, struct stat
*st_data
)
314 char *p
= map_path(path
);
315 if (unlikely(p
== NULL
))
317 int res
= lstat(p
, st_data
);
319 return (res
== -1) ? -errno
: 0;
322 static int ciopfs_fgetattr(const char *path
, struct stat
*stbuf
,
323 struct fuse_file_info
*fi
)
325 int res
= fstat(fi
->fh
, stbuf
);
332 static int ciopfs_readlink(const char *path
, char *buf
, size_t size
)
334 char *p
= map_path(path
);
335 if (unlikely(p
== NULL
))
337 enter_user_context();
338 int res
= readlink(p
, buf
, size
- 1);
339 leave_user_context();
347 static int ciopfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
348 off_t offset
, struct fuse_file_info
*fi
)
353 char *p
= map_path(path
);
354 if (unlikely(p
== NULL
))
356 size_t pathlen
= strlen(p
);
357 char dnamebuf
[PATH_MAX
];
358 char attrbuf
[FILENAME_MAX
];
360 if (pathlen
> PATH_MAX
) {
376 while ((de
= readdir(dp
)) != NULL
) {
381 /* skip any entry which is not all lower case for now */
382 if (str_contains_upper(de
->d_name
))
385 memset(&st
, 0, sizeof(st
));
386 st
.st_ino
= de
->d_ino
;
387 st
.st_mode
= de
->d_type
<< 12;
389 if (!strcmp(".", de
->d_name
) || !strcmp("..", de
->d_name
))
392 /* check whether there is an original name associated with
393 * this path and if so return it instead of the all lower
396 snprintf(dnamebuf
, sizeof dnamebuf
, "%s/%s", p
, de
->d_name
);
397 debug("dnamebuf: %s de->d_name: %s\n", dnamebuf
, de
->d_name
);
398 if (ciopfs_get_orig_name(dnamebuf
, attrbuf
, sizeof attrbuf
) > 0) {
399 /* we found an original name now check whether it is
400 * still accurate and if not remove it
402 attrlower
= str_fold(attrbuf
);
403 if (attrlower
&& !strcmp(attrlower
, de
->d_name
))
407 ciopfs_remove_orig_name(dnamebuf
);
413 debug("dname: %s\n", dname
);
414 if (filler(buf
, dname
, &st
, 0))
424 static int ciopfs_mknod(const char *path
, mode_t mode
, dev_t rdev
)
427 char *p
= map_path(path
);
428 if (unlikely(p
== NULL
))
430 enter_user_context();
431 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
434 res
= open(p
, O_CREAT
| O_EXCL
| O_WRONLY
, mode
);
436 ciopfs_set_orig_name_fd(res
, path
);
439 } else if (S_ISFIFO(mode
)) {
440 res
= mkfifo(p
, mode
);
442 res
= mknod(p
, mode
, rdev
);
443 leave_user_context();
451 static int ciopfs_mkdir(const char *path
, mode_t mode
)
454 char *p
= map_path(path
);
455 if (unlikely(p
== NULL
))
457 enter_user_context();
458 int res
= mkdir(p
, mode
);
459 leave_user_context();
466 ciopfs_set_orig_name_path(p
, path
);
472 static int ciopfs_unlink(const char *path
)
474 char *p
= map_path(path
);
475 if (unlikely(p
== NULL
))
477 enter_user_context();
479 leave_user_context();
486 static int ciopfs_rmdir(const char *path
)
488 char *p
= map_path(path
);
489 if (unlikely(p
== NULL
))
491 enter_user_context();
493 leave_user_context();
500 static int ciopfs_symlink(const char *from
, const char *to
)
503 char *f
= map_path(from
);
504 char *t
= map_path(to
);
505 if (unlikely(f
== NULL
|| t
== NULL
))
507 enter_user_context();
508 int res
= symlink(f
, t
);
509 leave_user_context();
514 ciopfs_set_orig_name_path(t
, to
);
521 static int ciopfs_rename(const char *from
, const char *to
)
524 char *f
= map_path(from
);
525 char *t
= map_path(to
);
526 if (unlikely(f
== NULL
|| t
== NULL
))
528 enter_user_context();
529 int res
= rename(f
, t
);
530 leave_user_context();
535 ciopfs_set_orig_name_path(t
, to
);
542 static int ciopfs_link(const char *from
, const char *to
)
545 char *f
= map_path(from
);
546 char *t
= map_path(to
);
547 if (unlikely(f
== NULL
|| t
== NULL
))
549 enter_user_context();
550 int res
= link(f
, t
);
551 leave_user_context();
556 ciopfs_set_orig_name_path(t
, to
);
563 static int ciopfs_chmod(const char *path
, mode_t mode
)
565 char *p
= map_path(path
);
566 if (unlikely(p
== NULL
))
568 enter_user_context();
569 int res
= chmod(p
, mode
);
570 leave_user_context();
577 static int ciopfs_chown(const char *path
, uid_t uid
, gid_t gid
)
579 char *p
= map_path(path
);
580 if (unlikely(p
== NULL
))
582 enter_user_context();
583 int res
= lchown(p
, uid
, gid
);
584 leave_user_context();
591 static int ciopfs_truncate(const char *path
, off_t size
)
593 char *p
= map_path(path
);
594 if (unlikely(p
== NULL
))
596 enter_user_context();
597 int res
= truncate(p
, size
);
598 leave_user_context();
605 static int ciopfs_ftruncate(const char *path
, off_t size
, struct fuse_file_info
*fi
)
607 enter_user_context();
608 int res
= ftruncate(fi
->fh
, size
);
609 leave_user_context();
616 static int ciopfs_utimens(const char *path
, const struct timespec ts
[2])
618 char *p
= map_path(path
);
619 if (unlikely(p
== NULL
))
621 struct timeval tv
[2];
623 tv
[0].tv_sec
= ts
[0].tv_sec
;
624 tv
[0].tv_usec
= ts
[0].tv_nsec
/ 1000;
625 tv
[1].tv_sec
= ts
[1].tv_sec
;
626 tv
[1].tv_usec
= ts
[1].tv_nsec
/ 1000;
628 enter_user_context();
629 int res
= utimes(p
, tv
);
630 leave_user_context();
637 static int ciopfs_create(const char *path
, mode_t mode
, struct fuse_file_info
*fi
)
639 char *p
= map_path(path
);
640 if (unlikely(p
== NULL
))
642 enter_user_context();
643 int fd
= open(p
, fi
->flags
, mode
);
644 leave_user_context();
648 ciopfs_set_orig_name_fd(fd
, path
);
653 static int ciopfs_open(const char *path
, struct fuse_file_info
*fi
)
655 char *p
= map_path(path
);
656 if (unlikely(p
== NULL
))
658 enter_user_context();
659 int fd
= open(p
, fi
->flags
);
660 leave_user_context();
664 if (fi
->flags
& O_CREAT
)
665 ciopfs_set_orig_name_fd(fd
, path
);
670 static int ciopfs_read(const char *path
, char *buf
, size_t size
, off_t offset
,
671 struct fuse_file_info
*fi
)
673 int res
= pread(fi
->fh
, buf
, size
, offset
);
679 static int ciopfs_write(const char *path
, const char *buf
, size_t size
,
680 off_t offset
, struct fuse_file_info
*fi
)
682 int res
= pwrite(fi
->fh
, buf
, size
, offset
);
688 static int ciopfs_statfs(const char *path
, struct statvfs
*stbuf
)
690 char *p
= map_path(path
);
691 if (unlikely(p
== NULL
))
693 enter_user_context();
694 int res
= statvfs(p
, stbuf
);
695 leave_user_context();
703 static int ciopfs_flush(const char *path
, struct fuse_file_info
*fi
)
705 /* This is called from every close on an open file, so call the
706 close on the underlying filesystem. But since flush may be
707 called multiple times for an open file, this must not really
708 close the file. This is important if used on a network
709 filesystem like NFS which flush the data/metadata on close() */
710 int res
= close(dup(fi
->fh
));
717 static int ciopfs_release(const char *path
, struct fuse_file_info
*fi
)
723 static int ciopfs_fsync(const char *path
, int isdatasync
, struct fuse_file_info
*fi
)
726 #ifdef HAVE_FDATASYNC
728 res
= fdatasync(fi
->fh
);
737 static int ciopfs_access(const char *path
, int mode
)
739 char *p
= map_path(path
);
740 if (unlikely(p
== NULL
))
742 enter_user_context();
743 int res
= access(p
, mode
);
744 leave_user_context();
751 static int ciopfs_setxattr(const char *path
, const char *name
, const char *value
,
752 size_t size
, int flags
)
754 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
755 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
758 char *p
= map_path(path
);
759 if (unlikely(p
== NULL
))
761 enter_user_context();
762 int res
= lsetxattr(p
, name
, value
, size
, flags
);
763 leave_user_context();
770 static int ciopfs_getxattr(const char *path
, const char *name
, char *value
, size_t size
)
772 char *p
= map_path(path
);
773 if (unlikely(p
== NULL
))
775 enter_user_context();
776 int res
= lgetxattr(p
, name
, value
, size
);
777 leave_user_context();
784 static int ciopfs_listxattr(const char *path
, char *list
, size_t size
)
786 char *p
= map_path(path
);
787 if (unlikely(p
== NULL
))
789 enter_user_context();
790 int res
= llistxattr(p
, list
, size
);
791 leave_user_context();
798 static int ciopfs_removexattr(const char *path
, const char *name
)
800 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
801 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
804 char *p
= map_path(path
);
805 if (unlikely(p
== NULL
))
807 enter_user_context();
808 int res
= lremovexattr(p
, name
);
809 leave_user_context();
816 static int ciopfs_lock(const char *path
, struct fuse_file_info
*fi
, int cmd
,
819 return ulockmgr_op(fi
->fh
, cmd
, lock
, &fi
->lock_owner
,
820 sizeof(fi
->lock_owner
));
823 static void *ciopfs_init(struct fuse_conn_info
*conn
)
825 if (chdir(dirname
) == -1) {
826 log_print("init: %s\n", strerror(errno
));
832 struct fuse_operations ciopfs_operations
= {
833 .getattr
= ciopfs_getattr
,
834 .fgetattr
= ciopfs_fgetattr
,
835 .readlink
= ciopfs_readlink
,
836 .readdir
= ciopfs_readdir
,
837 .mknod
= ciopfs_mknod
,
838 .mkdir
= ciopfs_mkdir
,
839 .symlink
= ciopfs_symlink
,
840 .unlink
= ciopfs_unlink
,
841 .rmdir
= ciopfs_rmdir
,
842 .rename
= ciopfs_rename
,
844 .chmod
= ciopfs_chmod
,
845 .chown
= ciopfs_chown
,
846 .truncate
= ciopfs_truncate
,
847 .ftruncate
= ciopfs_ftruncate
,
848 .utimens
= ciopfs_utimens
,
849 .create
= ciopfs_create
,
852 .write
= ciopfs_write
,
853 .statfs
= ciopfs_statfs
,
854 .flush
= ciopfs_flush
,
855 .release
= ciopfs_release
,
856 .fsync
= ciopfs_fsync
,
857 .access
= ciopfs_access
,
858 .setxattr
= ciopfs_setxattr
,
859 .getxattr
= ciopfs_getxattr
,
860 .listxattr
= ciopfs_listxattr
,
861 .removexattr
= ciopfs_removexattr
,
872 static void usage(const char *name
)
874 fprintf(stderr
, "usage: %s directory mountpoint [options]\n"
876 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
879 " -o opt,[opt...] mount options\n"
880 " -h|--help print help\n"
881 " --version print version\n"
891 static int ciopfs_opt_parse(void *data
, const char *arg
, int key
, struct fuse_args
*outargs
)
894 case FUSE_OPT_KEY_NONOPT
:
896 /* XXX: realpath(char *s, NULL) is a glibc extension */
897 if (!(dirname
= realpath(arg
, NULL
))) {
898 perror(outargs
->argv
[0]);
904 case FUSE_OPT_KEY_OPT
:
909 dolog
= stderr_print
;
913 case CIOPFS_OPT_HELP
:
914 usage(outargs
->argv
[0]);
915 fuse_opt_add_arg(outargs
, "-ho");
916 fuse_main(outargs
->argc
, outargs
->argv
, &ciopfs_operations
, NULL
);
918 case CIOPFS_OPT_VERSION
:
919 fprintf(stderr
, "%s: "VERSION
" fuse: %d\n", outargs
->argv
[0], fuse_version());
922 fprintf(stderr
, "see `%s -h' for usage\n", outargs
->argv
[0]);
928 static struct fuse_opt ciopfs_opts
[] = {
929 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP
),
930 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP
),
931 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION
),
935 int main(int argc
, char *argv
[])
937 struct fuse_args args
= FUSE_ARGS_INIT(argc
, argv
);
938 if (fuse_opt_parse(&args
, &dirname
, ciopfs_opts
, ciopfs_opt_parse
)) {
939 fprintf(stderr
, "Invalid arguments, see `%s -h' for usage\n", argv
[0]);
943 return fuse_main(args
.argc
, args
.argv
, &ciopfs_operations
, NULL
);