| /* |
| fuse iconv module: file name charset conversion |
| Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu> |
| |
| This program can be distributed under the terms of the GNU LGPLv2. |
| See the file COPYING.LIB |
| */ |
| |
| #define FUSE_USE_VERSION 30 |
| |
| #include <config.h> |
| |
| #include <fuse.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <iconv.h> |
| #include <pthread.h> |
| #include <locale.h> |
| #include <langinfo.h> |
| |
| struct iconv { |
| struct fuse_fs *next; |
| pthread_mutex_t lock; |
| char *from_code; |
| char *to_code; |
| iconv_t tofs; |
| iconv_t fromfs; |
| }; |
| |
| struct iconv_dh { |
| struct iconv *ic; |
| void *prev_buf; |
| fuse_fill_dir_t prev_filler; |
| }; |
| |
| static struct iconv *iconv_get(void) |
| { |
| return fuse_get_context()->private_data; |
| } |
| |
| static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp, |
| int fromfs) |
| { |
| size_t pathlen; |
| size_t newpathlen; |
| char *newpath; |
| size_t plen; |
| char *p; |
| size_t res; |
| int err; |
| |
| if (path == NULL) { |
| *newpathp = NULL; |
| return 0; |
| } |
| |
| pathlen = strlen(path); |
| newpathlen = pathlen * 4; |
| newpath = malloc(newpathlen + 1); |
| if (!newpath) |
| return -ENOMEM; |
| |
| plen = newpathlen; |
| p = newpath; |
| pthread_mutex_lock(&ic->lock); |
| do { |
| res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path, |
| &pathlen, &p, &plen); |
| if (res == (size_t) -1) { |
| char *tmp; |
| size_t inc; |
| |
| err = -EILSEQ; |
| if (errno != E2BIG) |
| goto err; |
| |
| inc = (pathlen + 1) * 4; |
| newpathlen += inc; |
| tmp = realloc(newpath, newpathlen + 1); |
| err = -ENOMEM; |
| if (!tmp) |
| goto err; |
| |
| p = tmp + (p - newpath); |
| plen += inc; |
| newpath = tmp; |
| } |
| } while (res == (size_t) -1); |
| pthread_mutex_unlock(&ic->lock); |
| *p = '\0'; |
| *newpathp = newpath; |
| return 0; |
| |
| err: |
| iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL); |
| pthread_mutex_unlock(&ic->lock); |
| free(newpath); |
| return err; |
| } |
| |
| static int iconv_getattr(const char *path, struct stat *stbuf, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_getattr(ic->next, newpath, stbuf, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_access(const char *path, int mask) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_access(ic->next, newpath, mask); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_readlink(const char *path, char *buf, size_t size) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_readlink(ic->next, newpath, buf, size); |
| if (!err) { |
| char *newlink; |
| err = iconv_convpath(ic, buf, &newlink, 1); |
| if (!err) { |
| strncpy(buf, newlink, size - 1); |
| buf[size - 1] = '\0'; |
| free(newlink); |
| } |
| } |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_opendir(const char *path, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_opendir(ic->next, newpath, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_dir_fill(void *buf, const char *name, |
| const struct stat *stbuf, off_t off, |
| enum fuse_fill_dir_flags flags) |
| { |
| struct iconv_dh *dh = buf; |
| char *newname; |
| int res = 0; |
| if (iconv_convpath(dh->ic, name, &newname, 1) == 0) { |
| res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags); |
| free(newname); |
| } |
| return res; |
| } |
| |
| static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler, |
| off_t offset, struct fuse_file_info *fi, |
| enum fuse_readdir_flags flags) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| struct iconv_dh dh; |
| dh.ic = ic; |
| dh.prev_buf = buf; |
| dh.prev_filler = filler; |
| err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill, |
| offset, fi, flags); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_releasedir(const char *path, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_releasedir(ic->next, newpath, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_mknod(const char *path, mode_t mode, dev_t rdev) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_mknod(ic->next, newpath, mode, rdev); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_mkdir(const char *path, mode_t mode) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_mkdir(ic->next, newpath, mode); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_unlink(const char *path) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_unlink(ic->next, newpath); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_rmdir(const char *path) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_rmdir(ic->next, newpath); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_symlink(const char *from, const char *to) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newfrom; |
| char *newto; |
| int err = iconv_convpath(ic, from, &newfrom, 0); |
| if (!err) { |
| err = iconv_convpath(ic, to, &newto, 0); |
| if (!err) { |
| err = fuse_fs_symlink(ic->next, newfrom, newto); |
| free(newto); |
| } |
| free(newfrom); |
| } |
| return err; |
| } |
| |
| static int iconv_rename(const char *from, const char *to, unsigned int flags) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newfrom; |
| char *newto; |
| int err = iconv_convpath(ic, from, &newfrom, 0); |
| if (!err) { |
| err = iconv_convpath(ic, to, &newto, 0); |
| if (!err) { |
| err = fuse_fs_rename(ic->next, newfrom, newto, flags); |
| free(newto); |
| } |
| free(newfrom); |
| } |
| return err; |
| } |
| |
| static int iconv_link(const char *from, const char *to) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newfrom; |
| char *newto; |
| int err = iconv_convpath(ic, from, &newfrom, 0); |
| if (!err) { |
| err = iconv_convpath(ic, to, &newto, 0); |
| if (!err) { |
| err = fuse_fs_link(ic->next, newfrom, newto); |
| free(newto); |
| } |
| free(newfrom); |
| } |
| return err; |
| } |
| |
| static int iconv_chmod(const char *path, mode_t mode, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_chmod(ic->next, newpath, mode, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_chown(const char *path, uid_t uid, gid_t gid, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_chown(ic->next, newpath, uid, gid, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_truncate(const char *path, off_t size, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_truncate(ic->next, newpath, size, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_utimens(const char *path, const struct timespec ts[2], |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_utimens(ic->next, newpath, ts, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_create(const char *path, mode_t mode, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_create(ic->next, newpath, mode, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_open_file(const char *path, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_open(ic->next, newpath, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp, |
| size_t size, off_t offset, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_write_buf(const char *path, struct fuse_bufvec *buf, |
| off_t offset, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_statfs(const char *path, struct statvfs *stbuf) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_statfs(ic->next, newpath, stbuf); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_flush(const char *path, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_flush(ic->next, newpath, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_release(const char *path, struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_release(ic->next, newpath, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_fsync(const char *path, int isdatasync, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_fsyncdir(const char *path, int isdatasync, |
| struct fuse_file_info *fi) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_setxattr(const char *path, const char *name, |
| const char *value, size_t size, int flags) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_setxattr(ic->next, newpath, name, value, size, |
| flags); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_getxattr(const char *path, const char *name, char *value, |
| size_t size) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_getxattr(ic->next, newpath, name, value, size); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_listxattr(const char *path, char *list, size_t size) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_listxattr(ic->next, newpath, list, size); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_removexattr(const char *path, const char *name) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_removexattr(ic->next, newpath, name); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd, |
| struct flock *lock) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_flock(const char *path, struct fuse_file_info *fi, int op) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_flock(ic->next, newpath, fi, op); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx) |
| { |
| struct iconv *ic = iconv_get(); |
| char *newpath; |
| int err = iconv_convpath(ic, path, &newpath, 0); |
| if (!err) { |
| err = fuse_fs_bmap(ic->next, newpath, blocksize, idx); |
| free(newpath); |
| } |
| return err; |
| } |
| |
| static void *iconv_init(struct fuse_conn_info *conn, |
| struct fuse_config *cfg) |
| { |
| struct iconv *ic = iconv_get(); |
| fuse_fs_init(ic->next, conn, cfg); |
| /* Don't touch cfg->nullpath_ok, we can work with |
| either */ |
| return ic; |
| } |
| |
| static void iconv_destroy(void *data) |
| { |
| struct iconv *ic = data; |
| fuse_fs_destroy(ic->next); |
| iconv_close(ic->tofs); |
| iconv_close(ic->fromfs); |
| pthread_mutex_destroy(&ic->lock); |
| free(ic->from_code); |
| free(ic->to_code); |
| free(ic); |
| } |
| |
| static const struct fuse_operations iconv_oper = { |
| .destroy = iconv_destroy, |
| .init = iconv_init, |
| .getattr = iconv_getattr, |
| .access = iconv_access, |
| .readlink = iconv_readlink, |
| .opendir = iconv_opendir, |
| .readdir = iconv_readdir, |
| .releasedir = iconv_releasedir, |
| .mknod = iconv_mknod, |
| .mkdir = iconv_mkdir, |
| .symlink = iconv_symlink, |
| .unlink = iconv_unlink, |
| .rmdir = iconv_rmdir, |
| .rename = iconv_rename, |
| .link = iconv_link, |
| .chmod = iconv_chmod, |
| .chown = iconv_chown, |
| .truncate = iconv_truncate, |
| .utimens = iconv_utimens, |
| .create = iconv_create, |
| .open = iconv_open_file, |
| .read_buf = iconv_read_buf, |
| .write_buf = iconv_write_buf, |
| .statfs = iconv_statfs, |
| .flush = iconv_flush, |
| .release = iconv_release, |
| .fsync = iconv_fsync, |
| .fsyncdir = iconv_fsyncdir, |
| .setxattr = iconv_setxattr, |
| .getxattr = iconv_getxattr, |
| .listxattr = iconv_listxattr, |
| .removexattr = iconv_removexattr, |
| .lock = iconv_lock, |
| .flock = iconv_flock, |
| .bmap = iconv_bmap, |
| }; |
| |
| static const struct fuse_opt iconv_opts[] = { |
| FUSE_OPT_KEY("-h", 0), |
| FUSE_OPT_KEY("--help", 0), |
| { "from_code=%s", offsetof(struct iconv, from_code), 0 }, |
| { "to_code=%s", offsetof(struct iconv, to_code), 1 }, |
| FUSE_OPT_END |
| }; |
| |
| static void iconv_help(void) |
| { |
| char *old = strdup(setlocale(LC_CTYPE, "")); |
| char *charmap = strdup(nl_langinfo(CODESET)); |
| setlocale(LC_CTYPE, old); |
| free(old); |
| printf( |
| " -o from_code=CHARSET original encoding of file names (default: UTF-8)\n" |
| " -o to_code=CHARSET new encoding of the file names (default: %s)\n", |
| charmap); |
| free(charmap); |
| } |
| |
| static int iconv_opt_proc(void *data, const char *arg, int key, |
| struct fuse_args *outargs) |
| { |
| (void) data; (void) arg; (void) outargs; |
| |
| if (!key) { |
| iconv_help(); |
| return -1; |
| } |
| |
| return 1; |
| } |
| |
| static struct fuse_fs *iconv_new(struct fuse_args *args, |
| struct fuse_fs *next[]) |
| { |
| struct fuse_fs *fs; |
| struct iconv *ic; |
| char *old = NULL; |
| const char *from; |
| const char *to; |
| |
| ic = calloc(1, sizeof(struct iconv)); |
| if (ic == NULL) { |
| fprintf(stderr, "fuse-iconv: memory allocation failed\n"); |
| return NULL; |
| } |
| |
| if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1) |
| goto out_free; |
| |
| if (!next[0] || next[1]) { |
| fprintf(stderr, "fuse-iconv: exactly one next filesystem required\n"); |
| goto out_free; |
| } |
| |
| from = ic->from_code ? ic->from_code : "UTF-8"; |
| to = ic->to_code ? ic->to_code : ""; |
| /* FIXME: detect charset equivalence? */ |
| if (!to[0]) |
| old = strdup(setlocale(LC_CTYPE, "")); |
| ic->tofs = iconv_open(from, to); |
| if (ic->tofs == (iconv_t) -1) { |
| fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n", |
| to, from); |
| goto out_free; |
| } |
| ic->fromfs = iconv_open(to, from); |
| if (ic->tofs == (iconv_t) -1) { |
| fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n", |
| from, to); |
| goto out_iconv_close_to; |
| } |
| if (old) { |
| setlocale(LC_CTYPE, old); |
| free(old); |
| } |
| |
| ic->next = next[0]; |
| fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic); |
| if (!fs) |
| goto out_iconv_close_from; |
| |
| return fs; |
| |
| out_iconv_close_from: |
| iconv_close(ic->fromfs); |
| out_iconv_close_to: |
| iconv_close(ic->tofs); |
| out_free: |
| free(ic->from_code); |
| free(ic->to_code); |
| free(ic); |
| if (old) { |
| setlocale(LC_CTYPE, old); |
| free(old); |
| } |
| return NULL; |
| } |
| |
| FUSE_REGISTER_MODULE(iconv, iconv_new); |