/* mhddfs - Multi HDD [FUSE] File System Copyright (C) 2008 Dmitry E. Oboukhov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Modified by Glenn Washburn (added support for extended attributes.) */ #define _XOPEN_SOURCE 500 #define _BSD_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #ifndef WITHOUT_XATTR #include #endif #include "parse_options.h" #include "tools.h" #include "debug.h" // getattr static int mhdd_stat(const char *file_name, struct stat *buf) { mhdd_debug(MHDD_MSG, "mhdd_stat: %s\n", file_name); char *path = find_path(file_name); if (path) { int ret = lstat(path, buf); free(path); if (ret == -1) return -errno; return 0; } errno = ENOENT; return -errno; } //statvfs static int mhdd_statfs(const char *path, struct statvfs *buf) { int i, j; struct statvfs * stats; struct stat st; dev_t * devices; mhdd_debug(MHDD_MSG, "mhdd_statfs: %s\n", path); stats = calloc(mhdd.cdirs, sizeof(struct statvfs)); devices = calloc(mhdd.cdirs, sizeof(dev_t)); for (i = 0; i < mhdd.cdirs; i++) { int ret = statvfs(mhdd.dirs[i], stats+i); if (ret != 0) { free(stats); free(devices); return -errno; } ret = stat(mhdd.dirs[i], &st); if (ret != 0) { free(stats); free(devices); return -errno; } devices[i] = st.st_dev; } unsigned long min_block = stats[0].f_bsize, min_frame = stats[0].f_frsize; for (i = 1; istats[i].f_bsize) min_block = stats[i].f_bsize; if (min_frame>stats[i].f_frsize) min_frame = stats[i].f_frsize; } if (!min_block) min_block = 512; if (!min_frame) min_frame = 512; for (i = 0; i < mhdd.cdirs; i++) { if (stats[i].f_bsize>min_block) { stats[i].f_bfree *= stats[i].f_bsize/min_block; stats[i].f_bavail *= stats[i].f_bsize/min_block; stats[i].f_bsize = min_block; } if (stats[i].f_frsize>min_frame) { stats[i].f_blocks *= stats[i].f_frsize/min_frame; stats[i].f_frsize = min_frame; } } memcpy(buf, stats, sizeof(struct statvfs)); for (i = 1; if_namemaxf_namemax = stats[i].f_namemax; } buf->f_ffree += stats[i].f_ffree; buf->f_files += stats[i].f_files; buf->f_favail += stats[i].f_favail; buf->f_bavail += stats[i].f_bavail; buf->f_bfree += stats[i].f_bfree; buf->f_blocks += stats[i].f_blocks; } free(stats); free(devices); return 0; } static int mhdd_readdir( const char *dirname, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info * fi) { int i, j, found; mhdd_debug(MHDD_MSG, "mhdd_readdir: %s\n", dirname); char **dirs = (char **) calloc(mhdd.cdirs+1, sizeof(char *)); struct dir_item { char *name; struct stat *st; struct dir_item *next, *prev; } *dir = 0, *item; struct stat st; // find all dirs for(i = j = found = 0; inext) { if (strcmp(prev->name, de->d_name) == 0) break; } if (prev) continue; // add item char *object_name = create_path(dirs[i], de->d_name); struct dir_item *new_item = calloc(1, sizeof(struct dir_item)); new_item->name = calloc(strlen(de->d_name) + 1, sizeof(char)); strcpy(new_item->name, de->d_name); new_item->st = calloc(1, sizeof(struct stat)); lstat(object_name, new_item->st); if (dir) { dir->prev = new_item; new_item->next = dir; } dir = new_item; free(object_name); } closedir(dh); } // fill list for(item = dir; item; item = item->next) { if (filler(buf, item->name, item->st, 0)) break; } // free memory while(dir) { free(dir->name); free(dir->st); if (dir->next) { dir = dir->next; free(dir->prev); continue; } free(dir); dir = 0; } for (i = 0; dirs[i]; i++) free(dirs[i]); free(dirs); return 0; } // readlink static int mhdd_readlink(const char *path, char *buf, size_t size) { mhdd_debug(MHDD_MSG, "mhdd_readlink: %s, size = %d\n", path, size); char *link = find_path(path); if (link) { memset(buf, 0, size); int res = readlink(link, buf, size); free(link); if (res >= 0) return 0; } return -1; } #define CREATE_FUNCTION 0 #define OPEN_FUNCION 1 // create or open static int mhdd_internal_open(const char *file, mode_t mode, struct fuse_file_info *fi, int what) { mhdd_debug(MHDD_INFO, "mhdd_internal_open: %s, flags = 0x%X\n", file, fi->flags); int dir_id, fd; char *path = find_path(file); if (path) { if (what == CREATE_FUNCTION) fd = open(path, fi->flags, mode); else fd = open(path, fi->flags); if (fd == -1) { free(path); return -errno; } struct flist *add = flist_create(file, path, fi->flags, fd); fi->fh = add->id; flist_unlock(); free(path); return 0; } mhdd_debug(MHDD_INFO, "mhdd_internal_open: new file %s\n", file); if ((dir_id = get_free_dir()) < 0) { errno = ENOSPC; return -errno; } create_parent_dirs(dir_id, file); path = create_path(mhdd.dirs[dir_id], file); if (what == CREATE_FUNCTION) fd = open(path, fi->flags, mode); else fd = open(path, fi->flags); if (fd == -1) { free(path); return -errno; } if (getuid() == 0) { struct stat st; gid_t gid = fuse_get_context()->gid; if (fstat(fd, &st) == 0) { /* parent directory is SGID'ed */ if (st.st_gid != getgid()) gid = st.st_gid; } fchown(fd, fuse_get_context()->uid, gid); } struct flist *add = flist_create(file, path, fi->flags, fd); fi->fh = add->id; flist_unlock(); free(path); return 0; } // create static int mhdd_create(const char *file, mode_t mode, struct fuse_file_info *fi) { mhdd_debug(MHDD_MSG, "mhdd_create: %s, mode = %X\n", file, fi->flags); int res = mhdd_internal_open(file, mode, fi, CREATE_FUNCTION); if (res != 0) mhdd_debug(MHDD_INFO, "mhdd_create: error: %s\n", strerror(-res)); return res; } // open static int mhdd_fileopen(const char *file, struct fuse_file_info *fi) { mhdd_debug(MHDD_MSG, "mhdd_fileopen: %s, flags = %04X\n", file, fi->flags); int res = mhdd_internal_open(file, 0, fi, OPEN_FUNCION); if (res != 0) mhdd_debug(MHDD_INFO, "mhdd_fileopen: error: %s\n", strerror(-res)); return res; } // close static int mhdd_release(const char *path, struct fuse_file_info *fi) { struct flist *del; int fh; mhdd_debug(MHDD_MSG, "mhdd_release: %s, handle = %lld\n", path, fi->fh); del = flist_item_by_id_wrlock(fi->fh); if (!del) { mhdd_debug(MHDD_INFO, "mhdd_release: unknown file number: %llu\n", fi->fh); errno = EBADF; return -errno; } fh = del->fh; flist_delete_wrlocked(del); close(fh); return 0; } // read static int mhdd_read(const char *path, char *buf, size_t count, off_t offset, struct fuse_file_info *fi) { ssize_t res; struct flist * info; mhdd_debug(MHDD_INFO, "mhdd_read: %s, offset = %lld, count = %lld\n", path, (long long)offset, (long long)count ); info = flist_item_by_id(fi->fh); if (!info) { errno = EBADF; return -errno; } res = pread(info->fh, buf, count, offset); flist_unlock(); if (res == -1) return -errno; return res; } // write static int mhdd_write(const char *path, const char *buf, size_t count, off_t offset, struct fuse_file_info *fi) { ssize_t res; struct flist *info; mhdd_debug(MHDD_INFO, "mhdd_write: %s, handle = %lld\n", path, fi->fh); info = flist_item_by_id(fi->fh); if (!info) { errno = EBADF; return -errno; } res = pwrite(info->fh, buf, count, offset); if ((res == count) || (res == -1 && errno != ENOSPC)) { flist_unlock(); if (res == -1) { mhdd_debug(MHDD_DEBUG, "mhdd_write: error write %s: %s\n", info->real_name, strerror(errno)); return -errno; } return res; } // end free space if (move_file(info, offset + count) == 0) { res = pwrite(info->fh, buf, count, offset); flist_unlock(); if (res == -1) { mhdd_debug(MHDD_DEBUG, "mhdd_write: error restart write: %s\n", strerror(errno)); return -errno; } if (res < count) { mhdd_debug(MHDD_DEBUG, "mhdd_write: error (re)write file %s %s\n", info->real_name, strerror(ENOSPC)); } return res; } errno = ENOSPC; flist_unlock(); return -errno; } // truncate static int mhdd_truncate(const char *path, off_t size) { char *file = find_path(path); mhdd_debug(MHDD_MSG, "mhdd_truncate: %s\n", path); if (file) { int res = truncate(file, size); free(file); if (res == -1) return -errno; return 0; } errno = ENOENT; return -errno; } // ftrucate static int mhdd_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) { int res; struct flist *info; mhdd_debug(MHDD_MSG, "mhdd_ftruncate: %s, handle = %lld\n", path, fi->fh); info = flist_item_by_id(fi->fh); if (!info) { errno = EBADF; return -errno; } int fh = info->fh; res = ftruncate(fh, size); flist_unlock(); if (res == -1) return -errno; return 0; } // access static int mhdd_access(const char *path, int mask) { mhdd_debug(MHDD_MSG, "mhdd_access: %s mode = %04X\n", path, mask); char *file = find_path(path); if (file) { int res = access(file, mask); free(file); if (res == -1) return -errno; return 0; } errno = ENOENT; return -errno; } // mkdir static int mhdd_mkdir(const char * path, mode_t mode) { mhdd_debug(MHDD_MSG, "mhdd_mkdir: %s mode = %04X\n", path, mode); if (find_path_id(path) != -1) { errno = EEXIST; return -errno; } char *parent = get_parent_path(path); if (!parent) { errno = EFAULT; return -errno; } if (find_path_id(parent) == -1) { free(parent); errno = EFAULT; return -errno; } free(parent); int dir_id = get_free_dir(); if (dir_id<0) { errno = ENOSPC; return -errno; } create_parent_dirs(dir_id, path); char *name = create_path(mhdd.dirs[dir_id], path); if (mkdir(name, mode) == 0) { if (getuid() == 0) { struct stat st; gid_t gid = fuse_get_context()->gid; if (lstat(name, &st) == 0) { /* parent directory is SGID'ed */ if (st.st_gid != getgid()) gid = st.st_gid; } chown(name, fuse_get_context()->uid, gid); } free(name); return 0; } free(name); return -errno; } // rmdir static int mhdd_rmdir(const char * path) { mhdd_debug(MHDD_MSG, "mhdd_rmdir: %s\n", path); char *dir; while((dir = find_path(path))) { int res = rmdir(dir); free(dir); if (res == -1) return -errno; } return 0; } // unlink static int mhdd_unlink(const char *path) { mhdd_debug(MHDD_MSG, "mhdd_unlink: %s\n", path); char *file = find_path(path); if (!file) { errno = ENOENT; return -errno; } int res = unlink(file); free(file); if (res == -1) return -errno; return 0; } // rename static int mhdd_rename(const char *from, const char *to) { mhdd_debug(MHDD_MSG, "mhdd_rename: from = %s to = %s\n", from, to); int i, res; struct stat sto, sfrom; char *obj_from, *obj_to; int from_is_dir = 0, to_is_dir = 0, from_is_file = 0, to_is_file = 0; int to_dir_is_empty = 1; if (strcmp(from, to) == 0) return 0; /* seek for possible errors */ for (i = 0; i < mhdd.cdirs; i++) { obj_to = create_path(mhdd.dirs[i], to); obj_from = create_path(mhdd.dirs[i], from); if (stat(obj_to, &sto) == 0) { if (S_ISDIR(sto.st_mode)) { to_is_dir++; if (!dir_is_empty(obj_to)) to_dir_is_empty = 0; } else to_is_file++; } if (stat(obj_from, &sfrom) == 0) { if (S_ISDIR (sfrom.st_mode)) from_is_dir++; else from_is_file++; } free(obj_from); free(obj_to); if (to_is_file && from_is_dir) return -ENOTDIR; if (to_is_file && to_is_dir) return -ENOTEMPTY; if (from_is_dir && !to_dir_is_empty) return -ENOTEMPTY; } /* parent 'to' path doesn't exists */ char *pto = get_parent_path (to); if (find_path_id(pto) == -1) { free (pto); return -ENOENT; } free (pto); /* rename cycle */ for (i = 0; i < mhdd.cdirs; i++) { obj_to = create_path(mhdd.dirs[i], to); obj_from = create_path(mhdd.dirs[i], from); if (stat(obj_from, &sfrom) == 0) { /* if from is dir and at the same time file, we only rename dir */ if (from_is_dir && from_is_file) { if (!S_ISDIR(sfrom.st_mode)) { free(obj_from); free(obj_to); continue; } } create_parent_dirs(i, to); mhdd_debug(MHDD_MSG, "mhdd_rename: rename %s -> %s\n", obj_from, obj_to); res = rename(obj_from, obj_to); if (res == -1) { free(obj_from); free(obj_to); return -errno; } } else { /* from and to are files, so we must remove to files */ if (from_is_file && to_is_file && !from_is_dir) { if (stat(obj_to, &sto) == 0) { mhdd_debug(MHDD_MSG, "mhdd_rename: unlink %s\n", obj_to); if (unlink(obj_to) == -1) { free(obj_from); free(obj_to); return -errno; } } } } free(obj_from); free(obj_to); } return 0; } // .utimens static int mhdd_utimens(const char *path, const struct timespec ts[2]) { mhdd_debug(MHDD_MSG, "mhdd_utimens: %s\n", path); int i, res, flag_found; for (i = flag_found = 0; i= 0) res = close(res); } else if (S_ISFIFO(mode)) res = mkfifo(nod, mode); else res = mknod(nod, mode, rdev); if (res != -1) { if (getuid() == 0) { struct fuse_context * fcontext = fuse_get_context(); chown(nod, fcontext->uid, fcontext->gid); } free(nod); return 0; } free(nod); if (errno != ENOSPC) return -errno; } return -errno; } #if _POSIX_SYNCHRONIZED_IO + 0 > 0 || defined(__FreeBSD__) #undef HAVE_FDATASYNC #else #define HAVE_FDATASYNC 1 #endif //fsync static int mhdd_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { struct flist *info; mhdd_debug(MHDD_MSG, "mhdd_fsync: path = %s handle = %llu\n", path, fi->fh); info = flist_item_by_id(fi->fh); int res; if (!info) { errno = EBADF; return -errno; } int fh = info->fh; #ifdef HAVE_FDATASYNC if (isdatasync) res = fdatasync(fh); else #endif res = fsync(fh); flist_unlock(); if (res == -1) return -errno; return 0; } // Define extended attribute support #ifndef WITHOUT_XATTR static int mhdd_setxattr(const char *path, const char *attrname, const char *attrval, size_t attrvalsize, int flags) { char * real_path = find_path(path); if (!real_path) return -ENOENT; mhdd_debug(MHDD_MSG, "mhdd_setxattr: path = %s name = %s value = %s size = %d\n", real_path, attrname, attrval, attrvalsize); int res = setxattr(real_path, attrname, attrval, attrvalsize, flags); free(real_path); if (res == -1) return -errno; return 0; } #endif #ifndef WITHOUT_XATTR static int mhdd_getxattr(const char *path, const char *attrname, char *buf, size_t count) { int size = 0; char * real_path = find_path(path); if (!real_path) return -ENOENT; mhdd_debug(MHDD_MSG, "mhdd_getxattr: path = %s name = %s bufsize = %d\n", real_path, attrname, count); size = getxattr(real_path, attrname, buf, count); free(real_path); if (size == -1) return -errno; return size; } #endif #ifndef WITHOUT_XATTR static int mhdd_listxattr(const char *path, char *buf, size_t count) { int ret = 0; char * real_path = find_path(path); if (!real_path) return -ENOENT; mhdd_debug(MHDD_MSG, "mhdd_listxattr: path = %s bufsize = %d\n", real_path, count); ret=listxattr(real_path, buf, count); free(real_path); if (ret == -1) return -errno; return ret; } #endif #ifndef WITHOUT_XATTR static int mhdd_removexattr(const char *path, const char *attrname) { char * real_path = find_path(path); if (!real_path) return -ENOENT; mhdd_debug(MHDD_MSG, "mhdd_removexattr: path = %s name = %s\n", real_path, attrname); int res = removexattr(real_path, attrname); free(real_path); if (res == -1) return -errno; return 0; } #endif // functions links static struct fuse_operations mhdd_oper = { .getattr = mhdd_stat, .statfs = mhdd_statfs, .readdir = mhdd_readdir, .readlink = mhdd_readlink, .open = mhdd_fileopen, .release = mhdd_release, .read = mhdd_read, .write = mhdd_write, .create = mhdd_create, .truncate = mhdd_truncate, .ftruncate = mhdd_ftruncate, .access = mhdd_access, .mkdir = mhdd_mkdir, .rmdir = mhdd_rmdir, .unlink = mhdd_unlink, .rename = mhdd_rename, .utimens = mhdd_utimens, .chmod = mhdd_chmod, .chown = mhdd_chown, .symlink = mhdd_symlink, .mknod = mhdd_mknod, .fsync = mhdd_fsync, .link = mhdd_link, #ifndef WITHOUT_XATTR .setxattr = mhdd_setxattr, .getxattr = mhdd_getxattr, .listxattr = mhdd_listxattr, .removexattr = mhdd_removexattr, #endif }; // start int main(int argc, char *argv[]) { mhdd_debug_init(); struct fuse_args *args = parse_options(argc, argv); flist_init(); return fuse_main(args->argc, args->argv, &mhdd_oper, 0); }