Extend introduction commentary.
[ciopfs.git] / ciopfs.c
blob593dbba7578fe4435f3b396fab0eba3b0f35feb6
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 <ctype.h>
59 #include <stdlib.h>
60 #include <stdbool.h>
61 #include <stdarg.h>
62 #include <string.h>
63 #include <errno.h>
64 #include <dirent.h>
65 #include <unistd.h>
66 #include <limits.h>
67 #include <syslog.h>
68 #include <grp.h>
70 #ifdef HAVE_LIBICUUC
71 #include <unicode/ustring.h>
72 #include <unicode/uchar.h>
73 #endif
75 #define log_print(format, args...) (*dolog)(format, ## args)
77 #ifdef NDEBUG
78 # define debug(format, args...)
79 #else
80 # define debug log_print
81 #endif
83 #define CIOPFS_ATTR_NAME "user.filename"
85 #ifndef PATH_MAX
86 #define PATH_MAX 4096
87 #endif
89 #ifndef FILENAME_MAX
90 #define FILENAME_MAX 4096
91 #endif
94 static const char *dirname;
96 void stderr_print(const char *fmt, ...)
98 va_list ap;
99 va_start(ap, fmt);
100 fputs("ciopfs: ", stderr);
101 vfprintf(stderr, fmt, ap);
102 va_end(ap);
105 void syslog_print(const char *fmt, ...)
107 va_list ap;
108 va_start(ap, fmt);
109 vsyslog(LOG_NOTICE, fmt, ap);
110 va_end(ap);
113 static void (*dolog)(const char *fmt, ...) = syslog_print;
115 #ifdef HAVE_LIBICUUC
117 static inline UChar *utf8_to_utf16(const char *str, int32_t *length)
119 UChar *ustr;
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));
126 if (!ustr)
127 return NULL;
128 u_strFromUTF8(ustr, *length, NULL, str, -1, &status);
129 if (U_FAILURE(status)) {
130 free(ustr);
131 return NULL;
133 return ustr;
136 static inline char *utf16_to_utf8(UChar *ustr, int32_t *length)
138 char *str;
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);
145 if (!str)
146 return NULL;
147 u_strToUTF8(str, *length, NULL, ustr, -1, &status);
148 if (U_FAILURE(status)) {
149 free(str);
150 return NULL;
152 return str;
155 static inline char *utf_fold(const char *s)
157 int32_t length;
158 char *str;
159 UChar *ustr;
160 UErrorCode status = U_ZERO_ERROR;
162 ustr = utf8_to_utf16(s, &length);
163 if (!ustr)
164 return NULL;
165 u_strFoldCase(ustr, length, ustr, length, U_FOLD_CASE_EXCLUDE_SPECIAL_I, &status);
166 if (U_FAILURE(status))
167 return NULL;
168 str = utf16_to_utf8(ustr, &length);
169 free(ustr);
170 return str;
173 static inline bool utf_contains_upper(const char *s)
175 bool ret = false;
176 int32_t length, i;
177 UChar32 c;
178 UChar *ustr = utf8_to_utf16(s, &length);
179 if (!ustr)
180 return true;
181 for (i = 0; i < length; /* U16_NEXT post-increments */) {
182 U16_NEXT(ustr, i, length, c);
183 if (u_isupper(c)) {
184 ret = true;
185 goto out;
188 out:
189 free(ustr);
190 return ret;
193 #endif /* HAVE_LIBICUUC */
195 static inline bool str_contains_upper(const char *s)
197 #ifdef HAVE_LIBICUUC
198 return utf_contains_upper(s);
199 #else
200 while (*s) {
201 if (isupper(*s++))
202 return true;
204 return false;
205 #endif
208 static inline char *str_fold(const char *src)
210 #ifdef HAVE_LIBICUUC
211 return utf_fold(src);
212 #else
213 char *t;
214 char *dest = malloc(strlen(src));
215 if (!dest)
216 return NULL;
217 for (t = dest; *src; src++, t++)
218 *t = tolower(*src);
219 *t = '\0';
220 return dest;
221 #endif
224 static char *map_path(const char *path)
226 char *p;
227 // XXX: malloc failure, memory fragmentation?
228 if (path[0] == '/') {
229 if (path[1] == '\0')
230 return strdup(".");
231 path++;
234 p = str_fold(path);
235 debug("%s => %s\n", path, p);
236 return 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
249 * available in
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;
261 size_t n = 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);
267 if (fd == -1)
268 return 0;
270 for (;;) {
271 if (!c) {
272 num_read = read(fd, buf, sizeof(buf) - 1);
273 if (num_read <= 0) {
274 close(fd);
275 return 0;
277 buf[num_read] = '\0';
278 s = buf;
281 c = *s++;
283 if (key[matched] == c) {
284 if (!key[++matched])
285 break;
287 } else
288 matched = (key[0] == c);
291 close(fd);
292 t = s;
293 n = 0;
295 while (*t != '\n') {
296 if (*t++ == ' ')
297 n++;
300 if (n == 0)
301 return 0;
303 *groups = gids = malloc(n * sizeof(gid_t));
304 if (!gids)
305 return 0;
306 n = 0;
308 while ((c = *s++) != '\n') {
309 if (c >= '0' && c <= '9')
310 grp = grp*10 + c - '0';
311 else if (c == ' ') {
312 gids[n++] = grp;
313 grp = 0;
317 return n;
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()
327 gid_t *groups;
328 size_t ngroups;
329 struct fuse_context *c = fuse_get_context();
331 if (getuid() || c->uid == 0)
332 return;
333 if ((ngroups = get_groups(&groups))) {
334 setgroups(ngroups, groups);
335 free(groups);
337 setegid(c->gid);
338 seteuid(c->uid);
341 static inline void leave_user_context()
343 if (getuid())
344 return;
346 seteuid(getuid());
347 setegid(getgid());
350 static ssize_t ciopfs_get_orig_name(const char *path, char *value, size_t size)
352 ssize_t attrlen;
353 debug("looking up original file name of %s ", path);
354 attrlen = lgetxattr(path, CIOPFS_ATTR_NAME, value, size);
355 if (attrlen > 0) {
356 value[attrlen] = '\0';
357 debug("found %s\n", value);
358 } else {
359 debug("nothing found\n");
361 return attrlen;
364 static int ciopfs_set_orig_name_fd(int fd, const char *origpath)
366 char *filename = strrchr(origpath, '/');
367 if (!filename)
368 filename = (char *)origpath;
369 else
370 filename++;
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));
375 return -errno;
377 return 0;
380 static int ciopfs_set_orig_name_path(const char *path, const char *origpath)
382 char *filename = strrchr(origpath, '/');
383 if (!filename)
384 filename = (char *)origpath;
385 else
386 filename++;
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));
391 return -errno;
393 return 0;
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);
406 free(p);
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);
414 if (res == -1)
415 return -errno;
416 return 0;
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();
426 free(p);
427 if (res == -1)
428 return -errno;
429 buf[res] = '\0';
430 return 0;
433 static int ciopfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
434 off_t offset, struct fuse_file_info *fi)
436 int ret = 0;
437 DIR *dp;
438 struct dirent *de;
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) {
445 ret = -ENAMETOOLONG;
446 goto out;
449 strcpy(dnamebuf, p);
451 (void) offset;
452 (void) fi;
454 dp = opendir(p);
455 if (dp == NULL) {
456 ret = -errno;
457 goto out;
460 while ((de = readdir(dp)) != NULL) {
461 struct stat st;
462 char *dname;
463 char *attrlower;
465 /* skip any entry which is not all lower case for now */
466 if (str_contains_upper(de->d_name))
467 continue;
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))
474 dname = de->d_name;
475 else {
476 /* check whether there is an original name associated with
477 * this path and if so return it instead of the all lower
478 * case one
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))
488 dname = attrbuf;
489 else {
490 dname = de->d_name;
491 ciopfs_remove_orig_name(dnamebuf);
493 free(attrlower);
494 } else
495 dname = de->d_name;
497 debug("dname: %s\n", dname);
498 if (filler(buf, dname, &st, 0))
499 break;
502 closedir(dp);
503 out:
504 free(p);
505 return ret;
508 static int ciopfs_mknod(const char *path, mode_t mode, dev_t rdev)
510 int res;
511 char *p = map_path(path);
512 enter_user_context();
513 /* On Linux this could just be 'mknod(p, mode, rdev)' but this
514 is more portable */
515 if (S_ISREG(mode)) {
516 res = open(p, O_CREAT | O_EXCL | O_WRONLY, mode);
517 if (res >= 0) {
518 ciopfs_set_orig_name_fd(res, path);
519 close(res);
521 } else if (S_ISFIFO(mode)) {
522 res = mkfifo(p, mode);
523 } else
524 res = mknod(p, mode, rdev);
525 leave_user_context();
526 free(p);
527 if (res == -1)
528 return -errno;
530 return 0;
533 static int ciopfs_mkdir(const char *path, mode_t mode)
535 int ret = 0;
536 char *p = map_path(path);
537 enter_user_context();
538 int res = mkdir(p, mode);
539 leave_user_context();
541 if (res == -1) {
542 ret = -errno;
543 goto out;
546 ciopfs_set_orig_name_path(p, path);
547 out:
548 free(p);
549 return ret;
552 static int ciopfs_unlink(const char *path)
554 char *p = map_path(path);
555 enter_user_context();
556 int res = unlink(p);
557 leave_user_context();
558 free(p);
559 if (res == -1)
560 return -errno;
561 return 0;
564 static int ciopfs_rmdir(const char *path)
566 char *p = map_path(path);
567 enter_user_context();
568 int res = rmdir(p);
569 leave_user_context();
570 free(p);
571 if (res == -1)
572 return -errno;
573 return 0;
576 static int ciopfs_symlink(const char *from, const char *to)
578 int ret = 0;
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();
584 if (res == -1) {
585 ret = -errno;
586 goto out;
588 ciopfs_set_orig_name_path(t, to);
589 out:
590 free(f);
591 free(t);
592 return ret;
595 static int ciopfs_rename(const char *from, const char *to)
597 int ret = 0;
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();
603 if (res == -1) {
604 ret = -errno;
605 goto out;
607 ciopfs_set_orig_name_path(t, to);
608 out:
609 free(f);
610 free(t);
611 return ret;
614 static int ciopfs_link(const char *from, const char *to)
616 int ret = 0;
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();
622 if (res == -1) {
623 ret = -errno;
624 goto out;
626 ciopfs_set_orig_name_path(t, to);
627 out:
628 free(f);
629 free(t);
630 return ret;
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();
639 free(p);
640 if (res == -1)
641 return -errno;
642 return 0;
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();
651 free(p);
652 if (res == -1)
653 return -errno;
654 return 0;
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();
663 free(p);
664 if (res == -1)
665 return -errno;
666 return 0;
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();
674 if (res == -1)
675 return -errno;
677 return 0;
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();
693 free(p);
694 if (res == -1)
695 return -errno;
696 return 0;
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();
705 free(p);
706 if (fd == -1)
707 return -errno;
708 ciopfs_set_orig_name_fd(fd, path);
709 fi->fh = fd;
710 return 0;
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();
719 free(p);
720 if (fd == -1)
721 return -errno;
722 if (fi->flags & O_CREAT)
723 ciopfs_set_orig_name_fd(fd, path);
724 fi->fh = fd;
725 return 0;
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);
732 if (res == -1)
733 res = -errno;
734 return res;
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);
741 if (res == -1)
742 res = -errno;
743 return res;
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();
752 free(p);
753 if (res == -1)
754 return -errno;
756 return 0;
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));
767 if (res == -1)
768 return -errno;
770 return 0;
773 static int ciopfs_release(const char *path, struct fuse_file_info *fi)
775 close(fi->fh);
776 return 0;
779 static int ciopfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi)
781 int res;
782 #ifdef HAVE_FDATASYNC
783 if (isdatasync)
784 res = fdatasync(fi->fh);
785 else
786 #endif
787 res = fsync(fi->fh);
788 if (res == -1)
789 return -errno;
790 return 0;
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();
799 free(p);
800 if (res == -1)
801 return -errno;
802 return 0;
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);
810 return -EPERM;
812 char *p = map_path(path);
813 enter_user_context();
814 int res = lsetxattr(p, name, value, size, flags);
815 leave_user_context();
816 free(p);
817 if (res == -1)
818 return -errno;
819 return 0;
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();
828 free(p);
829 if (res == -1)
830 return -errno;
831 return res;
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();
840 free(p);
841 if (res == -1)
842 return -errno;
843 return res;
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);
850 return -EPERM;
852 char *p = map_path(path);
853 enter_user_context();
854 int res = lremovexattr(p, name);
855 leave_user_context();
856 free(p);
857 if (res == -1)
858 return -errno;
859 return 0;
862 static int ciopfs_lock(const char *path, struct fuse_file_info *fi, int cmd,
863 struct flock *lock)
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));
873 exit(1);
875 return NULL;
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,
889 .link = ciopfs_link,
890 .chmod = ciopfs_chmod,
891 .chown = ciopfs_chown,
892 .truncate = ciopfs_truncate,
893 .ftruncate = ciopfs_ftruncate,
894 .utimens = ciopfs_utimens,
895 .create = ciopfs_create,
896 .open = ciopfs_open,
897 .read = ciopfs_read,
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,
908 .lock = ciopfs_lock,
909 .init = ciopfs_init
911 * what about:
913 * opendir
914 * releasedir
918 static void usage(const char *name)
920 fprintf(stderr, "usage: %s directory mountpoint [options]\n"
921 "\n"
922 "Mounts the content of directory at mountpoint in case insensitiv fashion.\n"
923 "\n"
924 "general options:\n"
925 " -o opt,[opt...] mount options\n"
926 " -h|--help print help\n"
927 " --version print version\n"
928 "\n", name);
932 enum {
933 CIOPFS_OPT_HELP,
934 CIOPFS_OPT_VERSION
937 static int ciopfs_opt_parse(void *data, const char *arg, int key, struct fuse_args *outargs)
939 switch (key) {
940 case FUSE_OPT_KEY_NONOPT:
941 if (!dirname) {
942 /* XXX: realpath(char *s, NULL) is a glibc extension */
943 if (!(dirname = realpath(arg, NULL))) {
944 perror(outargs->argv[0]);
945 exit(1);
947 return 0;
949 return 1;
950 case FUSE_OPT_KEY_OPT:
951 if (arg[0] == '-') {
952 switch (arg[1]) {
953 case 'd':
954 case 'f':
955 dolog = stderr_print;
958 return 1;
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);
963 exit(0);
964 case CIOPFS_OPT_VERSION:
965 fprintf(stderr, "%s: "VERSION" fuse: %d\n", outargs->argv[0], fuse_version());
966 exit(0);
967 default:
968 fprintf(stderr, "see `%s -h' for usage\n", outargs->argv[0]);
969 exit(1);
971 return 1;
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),
978 FUSE_OPT_END
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]);
986 exit(1);
988 umask(0);
989 return fuse_main(args.argc, args.argv, &ciopfs_operations, NULL);