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.
16 * In order to compile ciopfs, you will need both
17 * libfuse and libattr. Furthermore if you want a case
18 * preserving filesystem you have to make sure that the
19 * underlaying filesystem supports extended attributes
20 * (for example for ext{2,3} you need a kernel with
21 * CONFIG_EXT{2,3}_FS_XATTR enabled. You probably also
22 * want to mount the underlaying filesystem with the
23 * user_xattr option which allows non root users to create
24 * extended attributes.
32 * ciopfs directory mountpoint [options]
37 #define _XOPEN_SOURCE 500 /* For pread()/pwrite() */
40 #define _BSD_SOURCE /* for vsyslog() */
44 #include <sys/xattr.h>
59 #include <unicode/ustring.h>
60 #include <unicode/uchar.h>
63 #define logp(format, args...) (*dolog)(format, ## args)
66 # define debug(format, args...)
71 #define CIOPFS_ATTR_NAME "user.filename"
78 #define FILENAME_MAX 4096
82 static const char *dirname
;
84 void stderr_print(const char *fmt
, ...)
88 fputs("ciopfs: ", stderr
);
89 vfprintf(stderr
, fmt
, ap
);
93 void syslog_print(const char *fmt
, ...)
97 vsyslog(LOG_NOTICE
, fmt
, ap
);
101 static void (*dolog
)(const char *fmt
, ...) = syslog_print
;
105 static inline UChar
*utf8_to_utf16(const char *str
, int32_t *length
)
108 UErrorCode status
= U_ZERO_ERROR
;
110 u_strFromUTF8(NULL
, 0, length
, str
, -1, &status
);
111 status
= U_ZERO_ERROR
;
112 (*length
)++; /* for the NUL char */
113 ustr
= malloc(sizeof(UChar
) * (*length
));
116 u_strFromUTF8(ustr
, *length
, NULL
, str
, -1, &status
);
117 if (U_FAILURE(status
)) {
124 static inline char *utf16_to_utf8(UChar
*ustr
, int32_t *length
)
127 UErrorCode status
= U_ZERO_ERROR
;
129 u_strToUTF8(NULL
, 0, length
, ustr
, -1, &status
);
130 status
= U_ZERO_ERROR
;
131 (*length
)++; /* for the NUL char */
132 str
= malloc(*length
);
135 u_strToUTF8(str
, *length
, NULL
, ustr
, -1, &status
);
136 if (U_FAILURE(status
)) {
143 static inline char *utf_tolower(const char *s
)
148 UErrorCode status
= U_ZERO_ERROR
;
150 ustr
= utf8_to_utf16(s
, &length
);
153 u_strToLower(ustr
, length
, ustr
, length
, NULL
, &status
);
154 if (U_FAILURE(status
))
156 str
= utf16_to_utf8(ustr
, &length
);
161 static inline bool utf_contains_upper(const char *s
)
166 UChar
*ustr
= utf8_to_utf16(s
, &length
);
169 for (i
= 0; i
< length
; /* U16_NEXT post-increments */) {
170 U16_NEXT(s
, i
, length
, c
);
171 /* XXX: doesn't seem to work reliable */
182 #endif /* HAVE_LIBICUUC */
184 static inline bool str_contains_upper(const char *s
)
187 return utf_contains_upper(s
);
197 static inline char *str_tolower(const char *src
)
200 return utf_tolower(src
);
203 char *dest
= malloc(strlen(src
));
206 for (t
= dest
; *src
; src
++, t
++)
213 static char* map_path(const char *path
)
216 // XXX: malloc failure, memory fragmentation?
217 if (path
[0] == '/') {
223 p
= str_tolower(path
);
224 debug("%s => %s\n", path
, p
);
228 /* This only works when the fs is mounted by root.
229 * Further more it relies on the fact that the euid
230 * and egid are stored per thread.
233 static inline void enter_user_context()
238 struct fuse_context
*c
= fuse_get_context();
243 static inline void leave_user_context()
252 static ssize_t
ciopfs_get_orig_name(const char *path
, char *value
, size_t size
)
255 debug("looking up original file name of %s ", path
);
256 attrlen
= lgetxattr(path
, CIOPFS_ATTR_NAME
, value
, size
);
258 value
[attrlen
] = '\0';
259 debug("found %s\n", value
);
261 debug("nothing found\n");
266 static int ciopfs_set_orig_name_fd(int fd
, const char *origpath
)
268 char *filename
= strrchr(origpath
, '/');
270 filename
= (char *)origpath
;
273 //XXX: map_path memory leak
274 debug("storing original name '%s' in '%s'\n", filename
, map_path(origpath
));
275 if (fsetxattr(fd
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
276 debug("%s\n", strerror(errno
));
282 static int ciopfs_set_orig_name_path(const char *path
, const char *origpath
)
284 char *filename
= strrchr(origpath
, '/');
286 filename
= (char *)origpath
;
289 debug("storing original name '%s' in '%s'\n", filename
, path
);
290 // XXX: setting an extended attribute on a symlink doesn't seem to work (EPERM)
291 if (lsetxattr(path
, CIOPFS_ATTR_NAME
, filename
, strlen(filename
), 0)) {
292 debug("%s\n", strerror(errno
));
298 static int ciopfs_remove_orig_name(const char *path
)
300 debug("removing original file name of %s\n", path
);
301 return lremovexattr(path
, CIOPFS_ATTR_NAME
);
304 static int ciopfs_getattr(const char *path
, struct stat
*st_data
)
306 char *p
= map_path(path
);
307 int res
= lstat(p
, st_data
);
309 return (res
== -1) ? -errno
: 0;
312 static int ciopfs_fgetattr(const char *path
, struct stat
*stbuf
,
313 struct fuse_file_info
*fi
)
315 int res
= fstat(fi
->fh
, stbuf
);
322 static int ciopfs_readlink(const char *path
, char *buf
, size_t size
)
324 char *p
= map_path(path
);
325 enter_user_context();
326 int res
= readlink(p
, buf
, size
- 1);
327 leave_user_context();
335 static int ciopfs_readdir(const char *path
, void *buf
, fuse_fill_dir_t filler
,
336 off_t offset
, struct fuse_file_info
*fi
)
341 char *p
= map_path(path
);
342 size_t pathlen
= strlen(p
);
343 char dnamebuf
[PATH_MAX
];
344 char attrbuf
[FILENAME_MAX
];
346 if (pathlen
> PATH_MAX
) {
362 while ((de
= readdir(dp
)) != NULL
) {
367 /* skip any entry which is not all lower case for now */
368 if (str_contains_upper(de
->d_name
))
371 memset(&st
, 0, sizeof(st
));
372 st
.st_ino
= de
->d_ino
;
373 st
.st_mode
= de
->d_type
<< 12;
375 if (!strcmp(".", de
->d_name
) || !strcmp("..", de
->d_name
))
378 /* check whether there is an original name associated with
379 * this path and if so return it instead of the all lower
382 snprintf(dnamebuf
, sizeof dnamebuf
, "%s/%s", p
, de
->d_name
);
383 debug("dnamebuf: %s de->d_name: %s\n", dnamebuf
, de
->d_name
);
384 if (ciopfs_get_orig_name(dnamebuf
, attrbuf
, sizeof attrbuf
) > 0) {
385 /* we found an original name now check whether it is
386 * still accurate and if not remove it
388 attrlower
= str_tolower(attrbuf
);
389 if(attrlower
&& !strcmp(attrlower
, de
->d_name
))
393 ciopfs_remove_orig_name(dnamebuf
);
399 debug("dname: %s\n", dname
);
400 if (filler(buf
, dname
, &st
, 0))
410 static int ciopfs_mknod(const char *path
, mode_t mode
, dev_t rdev
)
413 char *p
= map_path(path
);
414 enter_user_context();
415 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
418 res
= open(p
, O_CREAT
| O_EXCL
| O_WRONLY
, mode
);
420 ciopfs_set_orig_name_fd(res
, path
);
423 } else if (S_ISFIFO(mode
)) {
424 res
= mkfifo(p
, mode
);
426 res
= mknod(p
, mode
, rdev
);
427 leave_user_context();
435 static int ciopfs_mkdir(const char *path
, mode_t mode
)
438 char *p
= map_path(path
);
439 enter_user_context();
440 int res
= mkdir(p
, mode
);
441 leave_user_context();
448 ciopfs_set_orig_name_path(p
, path
);
454 static int ciopfs_unlink(const char *path
)
456 char *p
= map_path(path
);
457 enter_user_context();
459 leave_user_context();
466 static int ciopfs_rmdir(const char *path
)
468 char *p
= map_path(path
);
469 enter_user_context();
471 leave_user_context();
478 static int ciopfs_symlink(const char *from
, const char *to
)
481 char *f
= map_path(from
);
482 char *t
= map_path(to
);
483 enter_user_context();
484 int res
= symlink(f
, t
);
485 leave_user_context();
490 ciopfs_set_orig_name_path(t
, to
);
497 static int ciopfs_rename(const char *from
, const char *to
)
500 char *f
= map_path(from
);
501 char *t
= map_path(to
);
502 enter_user_context();
503 int res
= rename(f
, t
);
504 leave_user_context();
509 ciopfs_set_orig_name_path(t
, to
);
516 static int ciopfs_link(const char *from
, const char *to
)
519 char *f
= map_path(from
);
520 char *t
= map_path(to
);
521 enter_user_context();
522 int res
= link(f
, t
);
523 leave_user_context();
528 ciopfs_set_orig_name_path(t
, to
);
535 static int ciopfs_chmod(const char *path
, mode_t mode
)
537 char *p
= map_path(path
);
538 enter_user_context();
539 int res
= chmod(p
, mode
);
540 leave_user_context();
547 static int ciopfs_chown(const char *path
, uid_t uid
, gid_t gid
)
549 char *p
= map_path(path
);
550 enter_user_context();
551 int res
= lchown(p
, uid
, gid
);
552 leave_user_context();
559 static int ciopfs_truncate(const char *path
, off_t size
)
561 char *p
= map_path(path
);
562 enter_user_context();
563 int res
= truncate(p
, size
);
564 leave_user_context();
571 static int ciopfs_ftruncate(const char *path
, off_t size
, struct fuse_file_info
*fi
)
573 enter_user_context();
574 int res
= ftruncate(fi
->fh
, size
);
575 leave_user_context();
582 static int ciopfs_utime(const char *path
, struct utimbuf
*buf
)
584 char *p
= map_path(path
);
585 enter_user_context();
586 int res
= utime(p
, buf
);
587 leave_user_context();
594 static int ciopfs_create(const char *path
, mode_t mode
, struct fuse_file_info
*fi
)
596 char *p
= map_path(path
);
597 enter_user_context();
598 int fd
= open(p
, fi
->flags
, mode
);
599 leave_user_context();
603 ciopfs_set_orig_name_fd(fd
, path
);
608 static int ciopfs_open(const char *path
, struct fuse_file_info
*fi
)
610 char *p
= map_path(path
);
611 enter_user_context();
612 int fd
= open(p
, fi
->flags
);
613 leave_user_context();
617 if (fi
->flags
& O_CREAT
)
618 ciopfs_set_orig_name_fd(fd
, path
);
623 static int ciopfs_read(const char *path
, char *buf
, size_t size
, off_t offset
,
624 struct fuse_file_info
*fi
)
626 int res
= pread(fi
->fh
, buf
, size
, offset
);
632 static int ciopfs_write(const char *path
, const char *buf
, size_t size
,
633 off_t offset
, struct fuse_file_info
*fi
)
635 int res
= pwrite(fi
->fh
, buf
, size
, offset
);
641 static int ciopfs_statfs(const char *path
, struct statvfs
*stbuf
)
643 char *p
= map_path(path
);
644 enter_user_context();
645 int res
= statvfs(p
, stbuf
);
646 leave_user_context();
654 static int ciopfs_flush(const char *path
, struct fuse_file_info
*fi
)
656 /* This is called from every close on an open file, so call the
657 close on the underlying filesystem. But since flush may be
658 called multiple times for an open file, this must not really
659 close the file. This is important if used on a network
660 filesystem like NFS which flush the data/metadata on close() */
661 int res
= close(dup(fi
->fh
));
668 static int ciopfs_release(const char *path
, struct fuse_file_info
*fi
)
674 static int ciopfs_fsync(const char *path
, int isdatasync
, struct fuse_file_info
*fi
)
677 #ifdef HAVE_FDATASYNC
679 res
= fdatasync(fi
->fh
);
688 static int ciopfs_access(const char *path
, int mode
)
690 char *p
= map_path(path
);
691 enter_user_context();
692 int res
= access(p
, mode
);
693 leave_user_context();
700 static int ciopfs_setxattr(const char *path
, const char *name
, const char *value
,
701 size_t size
, int flags
)
703 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
704 debug("denying setting value of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
707 char *p
= map_path(path
);
708 enter_user_context();
709 int res
= lsetxattr(p
, name
, value
, size
, flags
);
710 leave_user_context();
717 static int ciopfs_getxattr(const char *path
, const char *name
, char *value
, size_t size
)
719 char *p
= map_path(path
);
720 enter_user_context();
721 int res
= lgetxattr(p
, name
, value
, size
);
722 leave_user_context();
729 static int ciopfs_listxattr(const char *path
, char *list
, size_t size
)
731 char *p
= map_path(path
);
732 enter_user_context();
733 int res
= llistxattr(p
, list
, size
);
734 leave_user_context();
741 static int ciopfs_removexattr(const char *path
, const char *name
)
743 if (!strcmp(name
, CIOPFS_ATTR_NAME
)) {
744 debug("denying removal of extended attribute '%s'\n", CIOPFS_ATTR_NAME
);
747 char *p
= map_path(path
);
748 enter_user_context();
749 int res
= lremovexattr(p
, name
);
750 leave_user_context();
757 static int ciopfs_lock(const char *path
, struct fuse_file_info
*fi
, int cmd
,
760 return ulockmgr_op(fi
->fh
, cmd
, lock
, &fi
->lock_owner
,
761 sizeof(fi
->lock_owner
));
765 struct fuse_operations ciopfs_operations
= {
766 .getattr
= ciopfs_getattr
,
767 .fgetattr
= ciopfs_fgetattr
,
768 .readlink
= ciopfs_readlink
,
769 .readdir
= ciopfs_readdir
,
770 .mknod
= ciopfs_mknod
,
771 .mkdir
= ciopfs_mkdir
,
772 .symlink
= ciopfs_symlink
,
773 .unlink
= ciopfs_unlink
,
774 .rmdir
= ciopfs_rmdir
,
775 .rename
= ciopfs_rename
,
777 .chmod
= ciopfs_chmod
,
778 .chown
= ciopfs_chown
,
779 .truncate
= ciopfs_truncate
,
780 .ftruncate
= ciopfs_ftruncate
,
781 .utime
= ciopfs_utime
,
782 .create
= ciopfs_create
,
785 .write
= ciopfs_write
,
786 .statfs
= ciopfs_statfs
,
787 .flush
= ciopfs_flush
,
788 .release
= ciopfs_release
,
789 .fsync
= ciopfs_fsync
,
790 .access
= ciopfs_access
,
791 .setxattr
= ciopfs_setxattr
,
792 .getxattr
= ciopfs_getxattr
,
793 .listxattr
= ciopfs_listxattr
,
794 .removexattr
= ciopfs_removexattr
,
804 static void usage(const char *name
)
806 fprintf(stderr
, "usage: %s directory mountpoint [options]\n"
808 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
811 " -o opt,[opt...] mount options\n"
812 " -h|--help print help\n"
813 " --version print version\n"
823 static int ciopfs_opt_parse(void *data
, const char *arg
, int key
, struct fuse_args
*outargs
)
826 case FUSE_OPT_KEY_NONOPT
:
828 // XXX: realpath(char *s, NULL) is a glibc extension
829 if (!(dirname
= realpath(arg
, NULL
)) || chdir(arg
)) {
830 perror(outargs
->argv
[0]);
836 case FUSE_OPT_KEY_OPT
:
841 dolog
= stderr_print
;
845 case CIOPFS_OPT_HELP
:
846 usage(outargs
->argv
[0]);
847 fuse_opt_add_arg(outargs
, "-ho");
848 fuse_main(outargs
->argc
, outargs
->argv
, &ciopfs_operations
, NULL
);
850 case CIOPFS_OPT_VERSION
:
851 fprintf(stderr
, "%s: "VERSION
" fuse: %d\n", outargs
->argv
[0], fuse_version());
854 fprintf(stderr
, "see `%s -h' for usage\n", outargs
->argv
[0]);
860 static struct fuse_opt ciopfs_opts
[] = {
861 FUSE_OPT_KEY("-h", CIOPFS_OPT_HELP
),
862 FUSE_OPT_KEY("--help", CIOPFS_OPT_HELP
),
863 FUSE_OPT_KEY("--version", CIOPFS_OPT_VERSION
),
867 int main(int argc
, char *argv
[])
869 struct fuse_args args
= FUSE_ARGS_INIT(argc
, argv
);
870 if (fuse_opt_parse(&args
, &dirname
, ciopfs_opts
, ciopfs_opt_parse
)) {
871 fprintf(stderr
, "Invalid arguments, see `%s -h' for usage\n", argv
[0]);
874 debug("dir: %s\n", dirname
);
876 return fuse_main(args
.argc
, args
.argv
, &ciopfs_operations
, NULL
);