Wrap file system operations with {enter,leave}_user_context().
[ciopfs.git] / ciopfs.c
blob6f6ac0f7703034de0e043eee6a71e88c82f8eb1c
1 /*
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.
9 * How it works:
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 * Requirements:
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.
26 * Compile & Install:
27 * $EDITOR config.mk
28 * make
29 * sudo make install
31 * Mount:
32 * ciopfs directory mountpoint [options]
36 #ifdef __linux__
37 #define _XOPEN_SOURCE 500 /* For pread()/pwrite() */
38 #endif
40 #define _BSD_SOURCE /* for vsyslog() */
42 #include <fuse.h>
43 #include <ulockmgr.h>
44 #include <sys/xattr.h>
45 #include <assert.h>
46 #include <stdio.h>
47 #include <ctype.h>
48 #include <stdlib.h>
49 #include <stdbool.h>
50 #include <stdarg.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <dirent.h>
54 #include <unistd.h>
55 #include <limits.h>
56 #include <syslog.h>
58 #ifdef HAVE_LIBICUUC
59 #include <unicode/ustring.h>
60 #include <unicode/uchar.h>
61 #endif
63 #define logp(format, args...) (*dolog)(format, ## args)
65 #ifdef NDEBUG
66 # define debug(format, args...)
67 #else
68 # define debug logp
69 #endif
71 #define CIOPFS_ATTR_NAME "user.filename"
73 #ifndef PATH_MAX
74 #define PATH_MAX 4096
75 #endif
77 #ifndef FILENAME_MAX
78 #define FILENAME_MAX 4096
79 #endif
82 static const char *dirname;
84 void stderr_print(const char *fmt, ...)
86 va_list ap;
87 va_start(ap, fmt);
88 fputs("ciopfs: ", stderr);
89 vfprintf(stderr, fmt, ap);
90 va_end(ap);
93 void syslog_print(const char *fmt, ...)
95 va_list ap;
96 va_start(ap, fmt);
97 vsyslog(LOG_NOTICE, fmt, ap);
98 va_end(ap);
101 static void (*dolog)(const char *fmt, ...) = syslog_print;
103 #ifdef HAVE_LIBICUUC
105 static inline UChar *utf8_to_utf16(const char *str, int32_t *length)
107 UChar *ustr;
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));
114 if (!ustr)
115 return NULL;
116 u_strFromUTF8(ustr, *length, NULL, str, -1, &status);
117 if (U_FAILURE(status)) {
118 free(ustr);
119 return NULL;
121 return ustr;
124 static inline char *utf16_to_utf8(UChar *ustr, int32_t *length)
126 char *str;
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);
133 if (!str)
134 return NULL;
135 u_strToUTF8(str, *length, NULL, ustr, -1, &status);
136 if (U_FAILURE(status)) {
137 free(str);
138 return NULL;
140 return str;
143 static inline char *utf_tolower(const char *s)
145 int32_t length;
146 char *str;
147 UChar *ustr;
148 UErrorCode status = U_ZERO_ERROR;
150 ustr = utf8_to_utf16(s, &length);
151 if (!ustr)
152 return NULL;
153 u_strToLower(ustr, length, ustr, length, NULL, &status);
154 if (U_FAILURE(status))
155 return NULL;
156 str = utf16_to_utf8(ustr, &length);
157 free(ustr);
158 return str;
161 static inline bool utf_contains_upper(const char *s)
163 bool ret = false;
164 int32_t length, i;
165 UChar32 c;
166 UChar *ustr = utf8_to_utf16(s, &length);
167 if (!ustr)
168 return true;
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 */
172 if (u_isupper(c)) {
173 ret = true;
174 goto out;
177 out:
178 free(ustr);
179 return ret;
182 #endif /* HAVE_LIBICUUC */
184 static inline bool str_contains_upper(const char *s)
186 #ifdef HAVE_LIBICUUC
187 return utf_contains_upper(s);
188 #else
189 while(*s) {
190 if (isupper(*s++))
191 return true;
193 return false;
194 #endif
197 static inline char *str_tolower(const char *src)
199 #ifdef HAVE_LIBICUUC
200 return utf_tolower(src);
201 #else
202 char *t;
203 char *dest = malloc(strlen(src));
204 if (!dest)
205 return NULL;
206 for (t = dest; *src; src++, t++)
207 *t = tolower(*src);
208 *t = '\0';
209 return dest;
210 #endif
213 static char* map_path(const char *path)
215 char *p;
216 // XXX: malloc failure, memory fragmentation?
217 if (path[0] == '/') {
218 if (path[1] == '\0')
219 return strdup(".");
220 path++;
223 p = str_tolower(path);
224 debug("%s => %s\n", path, p);
225 return 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()
235 if(getuid())
236 return;
238 struct fuse_context *c = fuse_get_context();
239 setegid(c->gid);
240 seteuid(c->uid);
243 static inline void leave_user_context()
245 if(getuid())
246 return;
248 seteuid(getuid());
249 setegid(getgid());
252 static ssize_t ciopfs_get_orig_name(const char *path, char *value, size_t size)
254 ssize_t attrlen;
255 debug("looking up original file name of %s ", path);
256 attrlen = lgetxattr(path, CIOPFS_ATTR_NAME, value, size);
257 if (attrlen > 0) {
258 value[attrlen] = '\0';
259 debug("found %s\n", value);
260 } else {
261 debug("nothing found\n");
263 return attrlen;
266 static int ciopfs_set_orig_name_fd(int fd, const char *origpath)
268 char *filename = strrchr(origpath, '/');
269 if (!filename)
270 filename = (char *)origpath;
271 else
272 filename++;
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));
277 return -errno;
279 return 0;
282 static int ciopfs_set_orig_name_path(const char *path, const char *origpath)
284 char *filename = strrchr(origpath, '/');
285 if (!filename)
286 filename = (char *)origpath;
287 else
288 filename++;
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));
293 return -errno;
295 return 0;
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);
308 free(p);
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);
316 if (res == -1)
317 return -errno;
318 return 0;
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();
328 free(p);
329 if (res == -1)
330 return -errno;
331 buf[res] = '\0';
332 return 0;
335 static int ciopfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
336 off_t offset, struct fuse_file_info *fi)
338 int ret = 0;
339 DIR *dp;
340 struct dirent *de;
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) {
347 ret = -ENAMETOOLONG;
348 goto out;
351 strcpy(dnamebuf, p);
353 (void) offset;
354 (void) fi;
356 dp = opendir(p);
357 if (dp == NULL) {
358 ret = -errno;
359 goto out;
362 while ((de = readdir(dp)) != NULL) {
363 struct stat st;
364 char *dname;
365 char *attrlower;
367 /* skip any entry which is not all lower case for now */
368 if (str_contains_upper(de->d_name))
369 continue;
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))
376 dname = de->d_name;
377 else {
378 /* check whether there is an original name associated with
379 * this path and if so return it instead of the all lower
380 * case one
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))
390 dname = attrbuf;
391 else {
392 dname = de->d_name;
393 ciopfs_remove_orig_name(dnamebuf);
395 free(attrlower);
396 } else
397 dname = de->d_name;
399 debug("dname: %s\n", dname);
400 if (filler(buf, dname, &st, 0))
401 break;
404 closedir(dp);
405 out:
406 free(p);
407 return ret;
410 static int ciopfs_mknod(const char *path, mode_t mode, dev_t rdev)
412 int res;
413 char *p = map_path(path);
414 enter_user_context();
415 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
416 is more portable */
417 if (S_ISREG(mode)) {
418 res = open(p, O_CREAT | O_EXCL | O_WRONLY, mode);
419 if (res >= 0) {
420 ciopfs_set_orig_name_fd(res, path);
421 close(res);
423 } else if (S_ISFIFO(mode)) {
424 res = mkfifo(p, mode);
425 } else
426 res = mknod(p, mode, rdev);
427 leave_user_context();
428 free(p);
429 if (res == -1)
430 return -errno;
432 return 0;
435 static int ciopfs_mkdir(const char *path, mode_t mode)
437 int ret = 0;
438 char *p = map_path(path);
439 enter_user_context();
440 int res = mkdir(p, mode);
441 leave_user_context();
443 if (res == -1) {
444 ret = -errno;
445 goto out;
448 ciopfs_set_orig_name_path(p, path);
449 out:
450 free(p);
451 return ret;
454 static int ciopfs_unlink(const char *path)
456 char *p = map_path(path);
457 enter_user_context();
458 int res = unlink(p);
459 leave_user_context();
460 free(p);
461 if (res == -1)
462 return -errno;
463 return 0;
466 static int ciopfs_rmdir(const char *path)
468 char *p = map_path(path);
469 enter_user_context();
470 int res = rmdir(p);
471 leave_user_context();
472 free(p);
473 if (res == -1)
474 return -errno;
475 return 0;
478 static int ciopfs_symlink(const char *from, const char *to)
480 int ret = 0;
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();
486 if (res == -1) {
487 ret = -errno;
488 goto out;
490 ciopfs_set_orig_name_path(t, to);
491 out:
492 free(f);
493 free(t);
494 return ret;
497 static int ciopfs_rename(const char *from, const char *to)
499 int ret = 0;
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();
505 if (res == -1) {
506 ret = -errno;
507 goto out;
509 ciopfs_set_orig_name_path(t, to);
510 out:
511 free(f);
512 free(t);
513 return ret;
516 static int ciopfs_link(const char *from, const char *to)
518 int ret = 0;
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();
524 if (res == -1) {
525 ret = -errno;
526 goto out;
528 ciopfs_set_orig_name_path(t, to);
529 out:
530 free(f);
531 free(t);
532 return ret;
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();
541 free(p);
542 if (res == -1)
543 return -errno;
544 return 0;
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();
553 free(p);
554 if (res == -1)
555 return -errno;
556 return 0;
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();
565 free(p);
566 if (res == -1)
567 return -errno;
568 return 0;
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();
576 if (res == -1)
577 return -errno;
579 return 0;
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();
588 free(p);
589 if (res == -1)
590 return -errno;
591 return 0;
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();
600 free(p);
601 if(fd == -1)
602 return -errno;
603 ciopfs_set_orig_name_fd(fd, path);
604 fi->fh = fd;
605 return 0;
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();
614 free(p);
615 if (fd == -1)
616 return -errno;
617 if (fi->flags & O_CREAT)
618 ciopfs_set_orig_name_fd(fd, path);
619 fi->fh = fd;
620 return 0;
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);
627 if (res == -1)
628 res = -errno;
629 return res;
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);
636 if (res == -1)
637 res = -errno;
638 return res;
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();
647 free(p);
648 if (res == -1)
649 return -errno;
651 return 0;
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));
662 if (res == -1)
663 return -errno;
665 return 0;
668 static int ciopfs_release(const char *path, struct fuse_file_info *fi)
670 close(fi->fh);
671 return 0;
674 static int ciopfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi)
676 int res;
677 #ifdef HAVE_FDATASYNC
678 if (isdatasync)
679 res = fdatasync(fi->fh);
680 else
681 #endif
682 res = fsync(fi->fh);
683 if (res == -1)
684 return -errno;
685 return 0;
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();
694 free(p);
695 if (res == -1)
696 return -errno;
697 return 0;
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);
705 return -EPERM;
707 char *p = map_path(path);
708 enter_user_context();
709 int res = lsetxattr(p, name, value, size, flags);
710 leave_user_context();
711 free(p);
712 if (res == -1)
713 return -errno;
714 return 0;
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();
723 free(p);
724 if (res == -1)
725 return -errno;
726 return res;
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();
735 free(p);
736 if (res == -1)
737 return -errno;
738 return res;
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);
745 return -EPERM;
747 char *p = map_path(path);
748 enter_user_context();
749 int res = lremovexattr(p, name);
750 leave_user_context();
751 free(p);
752 if (res == -1)
753 return -errno;
754 return 0;
757 static int ciopfs_lock(const char *path, struct fuse_file_info *fi, int cmd,
758 struct flock *lock)
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,
776 .link = ciopfs_link,
777 .chmod = ciopfs_chmod,
778 .chown = ciopfs_chown,
779 .truncate = ciopfs_truncate,
780 .ftruncate = ciopfs_ftruncate,
781 .utime = ciopfs_utime,
782 .create = ciopfs_create,
783 .open = ciopfs_open,
784 .read = ciopfs_read,
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,
795 .lock = ciopfs_lock
797 * what about:
799 * opendir
800 * releasedir
804 static void usage(const char *name)
806 fprintf(stderr, "usage: %s directory mountpoint [options]\n"
807 "\n"
808 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
809 "\n"
810 "general options:\n"
811 " -o opt,[opt...] mount options\n"
812 " -h|--help print help\n"
813 " --version print version\n"
814 "\n", name);
818 enum {
819 CIOPFS_OPT_HELP,
820 CIOPFS_OPT_VERSION
823 static int ciopfs_opt_parse(void *data, const char *arg, int key, struct fuse_args *outargs)
825 switch(key) {
826 case FUSE_OPT_KEY_NONOPT:
827 if (!dirname) {
828 // XXX: realpath(char *s, NULL) is a glibc extension
829 if (!(dirname = realpath(arg, NULL)) || chdir(arg)) {
830 perror(outargs->argv[0]);
831 exit(1);
833 return 0;
835 return 1;
836 case FUSE_OPT_KEY_OPT:
837 if (arg[0] == '-') {
838 switch(arg[1]) {
839 case 'd':
840 case 'f':
841 dolog = stderr_print;
844 return 1;
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);
849 exit(0);
850 case CIOPFS_OPT_VERSION:
851 fprintf(stderr, "%s: "VERSION" fuse: %d\n", outargs->argv[0], fuse_version());
852 exit(0);
853 default:
854 fprintf(stderr, "see `%s -h' for usage\n", outargs->argv[0]);
855 exit(1);
857 return 1;
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),
864 FUSE_OPT_END
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]);
872 exit(1);
874 debug("dir: %s\n", dirname);
875 umask(0);
876 return fuse_main(args.argc, args.argv, &ciopfs_operations, NULL);