Set version to 0.2
[ciopfs.git] / ciopfs.c
blob5d065085fee4711fbb7538eda5daad43be8d76ba
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 * 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
18 * over.
20 * Requirements:
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.
36 * Compile & Install:
37 * $EDITOR config.mk
38 * make
39 * sudo make install
41 * Mount:
42 * ciopfs directory mountpoint [options]
46 #ifdef __linux__
47 #define _XOPEN_SOURCE 500 /* For pread()/pwrite() */
48 #endif
50 #define _BSD_SOURCE /* for vsyslog() */
52 #include <fuse.h>
53 #include <ulockmgr.h>
54 #include <sys/xattr.h>
55 #include <sys/time.h>
56 #include <assert.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <stdbool.h>
60 #include <stdarg.h>
61 #include <string.h>
62 #include <errno.h>
63 #include <dirent.h>
64 #include <unistd.h>
65 #include <limits.h>
66 #include <syslog.h>
67 #include <grp.h>
69 #if __GNUC__ >= 3
70 # define likely(x) __builtin_expect(!!(x), 1)
71 # define unlikely(x) __builtin_expect(!!(x), 0)
72 #else
73 # define likely(x) (x)
74 # define unlikely(x) (x)
75 #endif
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);
82 #ifdef HAVE_GLIB
83 # include "unicode-glib.c"
84 #elif defined HAVE_LIBICUUC
85 # include "unicode-icu.c"
86 #else
87 # include "ascii.c"
88 #endif
90 #define log_print(format, args...) (*dolog)(format, ## args)
92 #ifdef NDEBUG
93 # define debug(format, args...)
94 #else
95 # define debug log_print
96 #endif
98 #define CIOPFS_ATTR_NAME "user.filename"
100 #ifndef PATH_MAX
101 #define PATH_MAX 4096
102 #endif
104 #ifndef FILENAME_MAX
105 #define FILENAME_MAX 4096
106 #endif
108 static const char *dirname;
110 void stderr_print(const char *fmt, ...)
112 va_list ap;
113 va_start(ap, fmt);
114 fputs("ciopfs: ", stderr);
115 vfprintf(stderr, fmt, ap);
116 va_end(ap);
119 void syslog_print(const char *fmt, ...)
121 va_list ap;
122 va_start(ap, fmt);
123 vsyslog(LOG_NOTICE, fmt, ap);
124 va_end(ap);
127 static void (*dolog)(const char *fmt, ...) = syslog_print;
129 static char *map_path(const char *path)
131 char *p;
132 /* XXX: memory fragmentation? */
133 if (path[0] == '/') {
134 if (path[1] == '\0')
135 return strdup(".");
136 path++;
139 p = str_fold(path);
140 debug("%s => %s\n", path, p);
141 return 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
154 * available in
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;
166 size_t n = 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);
172 if (fd == -1)
173 return 0;
175 for (;;) {
176 if (!c) {
177 num_read = read(fd, buf, sizeof(buf) - 1);
178 if (num_read <= 0) {
179 close(fd);
180 return 0;
182 buf[num_read] = '\0';
183 s = buf;
186 c = *s++;
188 if (key[matched] == c) {
189 if (!key[++matched])
190 break;
192 } else
193 matched = (key[0] == c);
196 close(fd);
197 t = s;
198 n = 0;
200 while (*t != '\n') {
201 if (*t++ == ' ')
202 n++;
205 if (n == 0)
206 return 0;
208 *groups = gids = malloc(n * sizeof(gid_t));
209 if (!gids)
210 return 0;
211 n = 0;
213 while ((c = *s++) != '\n') {
214 if (c >= '0' && c <= '9')
215 grp = grp*10 + c - '0';
216 else if (c == ' ') {
217 gids[n++] = grp;
218 grp = 0;
222 return n;
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()
232 gid_t *groups;
233 size_t ngroups;
234 struct fuse_context *c = fuse_get_context();
236 if (getuid() || c->uid == 0)
237 return;
238 if ((ngroups = get_groups(&groups))) {
239 setgroups(ngroups, groups);
240 free(groups);
242 setegid(c->gid);
243 seteuid(c->uid);
246 static inline void leave_user_context()
248 if (getuid())
249 return;
251 seteuid(getuid());
252 setegid(getgid());
255 static ssize_t ciopfs_get_orig_name(const char *path, char *value, size_t size)
257 ssize_t attrlen;
258 debug("looking up original file name of %s ", path);
259 attrlen = lgetxattr(path, CIOPFS_ATTR_NAME, value, size);
260 if (attrlen > 0) {
261 value[attrlen] = '\0';
262 debug("found %s\n", value);
263 } else {
264 debug("nothing found\n");
266 return attrlen;
269 static int ciopfs_set_orig_name_fd(int fd, const char *origpath)
271 char *filename = strrchr(origpath, '/');
272 if (!filename)
273 filename = (char *)origpath;
274 else
275 filename++;
276 #ifndef NDEBUG
277 char *path = map_path(origpath);
278 if (likely(path != NULL)) {
279 log_print("storing original name '%s' in '%s'\n", filename, path);
280 free(path);
282 #endif
283 if (fsetxattr(fd, CIOPFS_ATTR_NAME, filename, strlen(filename), 0)) {
284 debug("%s\n", strerror(errno));
285 return -errno;
287 return 0;
290 static int ciopfs_set_orig_name_path(const char *path, const char *origpath)
292 char *filename = strrchr(origpath, '/');
293 if (!filename)
294 filename = (char *)origpath;
295 else
296 filename++;
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));
301 return -errno;
303 return 0;
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))
316 return -ENOMEM;
317 int res = lstat(p, st_data);
318 free(p);
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);
326 if (res == -1)
327 return -errno;
328 return 0;
332 static int ciopfs_readlink(const char *path, char *buf, size_t size)
334 char *p = map_path(path);
335 if (unlikely(p == NULL))
336 return -ENOMEM;
337 enter_user_context();
338 int res = readlink(p, buf, size - 1);
339 leave_user_context();
340 free(p);
341 if (res == -1)
342 return -errno;
343 buf[res] = '\0';
344 return 0;
347 static int ciopfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
348 off_t offset, struct fuse_file_info *fi)
350 int ret = 0;
351 DIR *dp;
352 struct dirent *de;
353 char *p = map_path(path);
354 if (unlikely(p == NULL))
355 return -ENOMEM;
356 size_t pathlen = strlen(p);
357 char dnamebuf[PATH_MAX];
358 char attrbuf[FILENAME_MAX];
360 if (pathlen > PATH_MAX) {
361 ret = -ENAMETOOLONG;
362 goto out;
365 strcpy(dnamebuf, p);
367 (void) offset;
368 (void) fi;
370 dp = opendir(p);
371 if (dp == NULL) {
372 ret = -errno;
373 goto out;
376 while ((de = readdir(dp)) != NULL) {
377 struct stat st;
378 char *dname;
379 char *attrlower;
381 /* skip any entry which is not all lower case for now */
382 if (str_contains_upper(de->d_name))
383 continue;
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))
390 dname = de->d_name;
391 else {
392 /* check whether there is an original name associated with
393 * this path and if so return it instead of the all lower
394 * case one
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))
404 dname = attrbuf;
405 else {
406 dname = de->d_name;
407 ciopfs_remove_orig_name(dnamebuf);
409 free(attrlower);
410 } else
411 dname = de->d_name;
413 debug("dname: %s\n", dname);
414 if (filler(buf, dname, &st, 0))
415 break;
418 closedir(dp);
419 out:
420 free(p);
421 return ret;
424 static int ciopfs_mknod(const char *path, mode_t mode, dev_t rdev)
426 int res;
427 char *p = map_path(path);
428 if (unlikely(p == NULL))
429 return -ENOMEM;
430 enter_user_context();
431 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
432 is more portable */
433 if (S_ISREG(mode)) {
434 res = open(p, O_CREAT | O_EXCL | O_WRONLY, mode);
435 if (res >= 0) {
436 ciopfs_set_orig_name_fd(res, path);
437 close(res);
439 } else if (S_ISFIFO(mode)) {
440 res = mkfifo(p, mode);
441 } else
442 res = mknod(p, mode, rdev);
443 leave_user_context();
444 free(p);
445 if (res == -1)
446 return -errno;
448 return 0;
451 static int ciopfs_mkdir(const char *path, mode_t mode)
453 int ret = 0;
454 char *p = map_path(path);
455 if (unlikely(p == NULL))
456 return -ENOMEM;
457 enter_user_context();
458 int res = mkdir(p, mode);
459 leave_user_context();
461 if (res == -1) {
462 ret = -errno;
463 goto out;
466 ciopfs_set_orig_name_path(p, path);
467 out:
468 free(p);
469 return ret;
472 static int ciopfs_unlink(const char *path)
474 char *p = map_path(path);
475 if (unlikely(p == NULL))
476 return -ENOMEM;
477 enter_user_context();
478 int res = unlink(p);
479 leave_user_context();
480 free(p);
481 if (res == -1)
482 return -errno;
483 return 0;
486 static int ciopfs_rmdir(const char *path)
488 char *p = map_path(path);
489 if (unlikely(p == NULL))
490 return -ENOMEM;
491 enter_user_context();
492 int res = rmdir(p);
493 leave_user_context();
494 free(p);
495 if (res == -1)
496 return -errno;
497 return 0;
500 static int ciopfs_symlink(const char *from, const char *to)
502 int ret = 0;
503 char *f = map_path(from);
504 char *t = map_path(to);
505 if (unlikely(f == NULL || t == NULL))
506 return -ENOMEM;
507 enter_user_context();
508 int res = symlink(f, t);
509 leave_user_context();
510 if (res == -1) {
511 ret = -errno;
512 goto out;
514 ciopfs_set_orig_name_path(t, to);
515 out:
516 free(f);
517 free(t);
518 return ret;
521 static int ciopfs_rename(const char *from, const char *to)
523 int ret = 0;
524 char *f = map_path(from);
525 char *t = map_path(to);
526 if (unlikely(f == NULL || t == NULL))
527 return -ENOMEM;
528 enter_user_context();
529 int res = rename(f, t);
530 leave_user_context();
531 if (res == -1) {
532 ret = -errno;
533 goto out;
535 ciopfs_set_orig_name_path(t, to);
536 out:
537 free(f);
538 free(t);
539 return ret;
542 static int ciopfs_link(const char *from, const char *to)
544 int ret = 0;
545 char *f = map_path(from);
546 char *t = map_path(to);
547 if (unlikely(f == NULL || t == NULL))
548 return -ENOMEM;
549 enter_user_context();
550 int res = link(f, t);
551 leave_user_context();
552 if (res == -1) {
553 ret = -errno;
554 goto out;
556 ciopfs_set_orig_name_path(t, to);
557 out:
558 free(f);
559 free(t);
560 return ret;
563 static int ciopfs_chmod(const char *path, mode_t mode)
565 char *p = map_path(path);
566 if (unlikely(p == NULL))
567 return -ENOMEM;
568 enter_user_context();
569 int res = chmod(p, mode);
570 leave_user_context();
571 free(p);
572 if (res == -1)
573 return -errno;
574 return 0;
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))
581 return -ENOMEM;
582 enter_user_context();
583 int res = lchown(p, uid, gid);
584 leave_user_context();
585 free(p);
586 if (res == -1)
587 return -errno;
588 return 0;
591 static int ciopfs_truncate(const char *path, off_t size)
593 char *p = map_path(path);
594 if (unlikely(p == NULL))
595 return -ENOMEM;
596 enter_user_context();
597 int res = truncate(p, size);
598 leave_user_context();
599 free(p);
600 if (res == -1)
601 return -errno;
602 return 0;
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();
610 if (res == -1)
611 return -errno;
613 return 0;
616 static int ciopfs_utimens(const char *path, const struct timespec ts[2])
618 char *p = map_path(path);
619 if (unlikely(p == NULL))
620 return -ENOMEM;
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();
631 free(p);
632 if (res == -1)
633 return -errno;
634 return 0;
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))
641 return -ENOMEM;
642 enter_user_context();
643 int fd = open(p, fi->flags, mode);
644 leave_user_context();
645 free(p);
646 if (fd == -1)
647 return -errno;
648 ciopfs_set_orig_name_fd(fd, path);
649 fi->fh = fd;
650 return 0;
653 static int ciopfs_open(const char *path, struct fuse_file_info *fi)
655 char *p = map_path(path);
656 if (unlikely(p == NULL))
657 return -ENOMEM;
658 enter_user_context();
659 int fd = open(p, fi->flags);
660 leave_user_context();
661 free(p);
662 if (fd == -1)
663 return -errno;
664 if (fi->flags & O_CREAT)
665 ciopfs_set_orig_name_fd(fd, path);
666 fi->fh = fd;
667 return 0;
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);
674 if (res == -1)
675 res = -errno;
676 return res;
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);
683 if (res == -1)
684 res = -errno;
685 return res;
688 static int ciopfs_statfs(const char *path, struct statvfs *stbuf)
690 char *p = map_path(path);
691 if (unlikely(p == NULL))
692 return -ENOMEM;
693 enter_user_context();
694 int res = statvfs(p, stbuf);
695 leave_user_context();
696 free(p);
697 if (res == -1)
698 return -errno;
700 return 0;
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));
711 if (res == -1)
712 return -errno;
714 return 0;
717 static int ciopfs_release(const char *path, struct fuse_file_info *fi)
719 close(fi->fh);
720 return 0;
723 static int ciopfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi)
725 int res;
726 #ifdef HAVE_FDATASYNC
727 if (isdatasync)
728 res = fdatasync(fi->fh);
729 else
730 #endif
731 res = fsync(fi->fh);
732 if (res == -1)
733 return -errno;
734 return 0;
737 static int ciopfs_access(const char *path, int mode)
739 char *p = map_path(path);
740 if (unlikely(p == NULL))
741 return -ENOMEM;
742 enter_user_context();
743 int res = access(p, mode);
744 leave_user_context();
745 free(p);
746 if (res == -1)
747 return -errno;
748 return 0;
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);
756 return -EPERM;
758 char *p = map_path(path);
759 if (unlikely(p == NULL))
760 return -ENOMEM;
761 enter_user_context();
762 int res = lsetxattr(p, name, value, size, flags);
763 leave_user_context();
764 free(p);
765 if (res == -1)
766 return -errno;
767 return 0;
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))
774 return -ENOMEM;
775 enter_user_context();
776 int res = lgetxattr(p, name, value, size);
777 leave_user_context();
778 free(p);
779 if (res == -1)
780 return -errno;
781 return res;
784 static int ciopfs_listxattr(const char *path, char *list, size_t size)
786 char *p = map_path(path);
787 if (unlikely(p == NULL))
788 return -ENOMEM;
789 enter_user_context();
790 int res = llistxattr(p, list, size);
791 leave_user_context();
792 free(p);
793 if (res == -1)
794 return -errno;
795 return res;
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);
802 return -EPERM;
804 char *p = map_path(path);
805 if (unlikely(p == NULL))
806 return -ENOMEM;
807 enter_user_context();
808 int res = lremovexattr(p, name);
809 leave_user_context();
810 free(p);
811 if (res == -1)
812 return -errno;
813 return 0;
816 static int ciopfs_lock(const char *path, struct fuse_file_info *fi, int cmd,
817 struct flock *lock)
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));
827 exit(1);
829 return NULL;
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,
843 .link = ciopfs_link,
844 .chmod = ciopfs_chmod,
845 .chown = ciopfs_chown,
846 .truncate = ciopfs_truncate,
847 .ftruncate = ciopfs_ftruncate,
848 .utimens = ciopfs_utimens,
849 .create = ciopfs_create,
850 .open = ciopfs_open,
851 .read = ciopfs_read,
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,
862 .lock = ciopfs_lock,
863 .init = ciopfs_init
865 * what about:
867 * opendir
868 * releasedir
872 static void usage(const char *name)
874 fprintf(stderr, "usage: %s directory mountpoint [options]\n"
875 "\n"
876 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
877 "\n"
878 "general options:\n"
879 " -o opt,[opt...] mount options\n"
880 " -h|--help print help\n"
881 " --version print version\n"
882 "\n", name);
886 enum {
887 CIOPFS_OPT_HELP,
888 CIOPFS_OPT_VERSION
891 static int ciopfs_opt_parse(void *data, const char *arg, int key, struct fuse_args *outargs)
893 switch (key) {
894 case FUSE_OPT_KEY_NONOPT:
895 if (!dirname) {
896 /* XXX: realpath(char *s, NULL) is a glibc extension */
897 if (!(dirname = realpath(arg, NULL))) {
898 perror(outargs->argv[0]);
899 exit(1);
901 return 0;
903 return 1;
904 case FUSE_OPT_KEY_OPT:
905 if (arg[0] == '-') {
906 switch (arg[1]) {
907 case 'd':
908 case 'f':
909 dolog = stderr_print;
912 return 1;
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);
917 exit(0);
918 case CIOPFS_OPT_VERSION:
919 fprintf(stderr, "%s: "VERSION" fuse: %d\n", outargs->argv[0], fuse_version());
920 exit(0);
921 default:
922 fprintf(stderr, "see `%s -h' for usage\n", outargs->argv[0]);
923 exit(1);
925 return 1;
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),
932 FUSE_OPT_END
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]);
940 exit(1);
942 umask(0);
943 return fuse_main(args.argc, args.argv, &ciopfs_operations, NULL);