/** * ntfsmount - Part of the Linux-NTFS project. * * Copyright (c) 2005 Yura Pakhuchiy * * NTFS module for FUSE. * * 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 2 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 (in the main directory of the Linux-NTFS * distribution in the file COPYING); if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SETXATTR #include #endif #include "attrib.h" #include "inode.h" #include "volume.h" #include "dir.h" #include "unistr.h" #include "layout.h" #include "index.h" #include "utils.h" #ifndef PATH_MAX #define PATH_MAX 4096 #endif typedef struct { fuse_fill_dir_t filler; void *buf; } ntfs_fuse_fill_context_t; typedef struct { ntfs_volume *vol; int state; long free_clusters; long free_mft; uid_t uid; gid_t gid; mode_t fmask; mode_t dmask; BOOL ro; BOOL show_sys_files; BOOL succeed_chmod; BOOL force; } ntfs_fuse_context_t; typedef enum { NF_FreeClustersOutdate = (1 << 0), /* Information about amount of free clusters is outdated. */ NF_FreeMFTOutdate = (1 << 1), /* Information about amount of free MFT records is outdated. */ } ntfs_fuse_state_bits; static struct options { char *mnt_point; /* Mount point */ char *options; /* Mount options */ int quiet; /* Less output */ int verbose; /* Extra output */ } opts; static const char *EXEC_NAME = "ntfsmount"; static char def_opts[] = "default_permissions,allow_other,"; static ntfs_fuse_context_t *ctx; GEN_PRINTF(Eprintf, stderr, NULL, FALSE) GEN_PRINTF(Vprintf, stderr, &opts.verbose, TRUE) GEN_PRINTF(Qprintf, stderr, &opts.quiet, FALSE) static long ntfs_fuse_get_nr_free_mft_records(ntfs_volume *vol) { u8 *buf; long nr_free = 0; s64 br, total = 0; if (!(ctx->state & NF_FreeMFTOutdate)) return ctx->free_mft; buf = malloc(NTFS_BUF_SIZE); if (!buf) return -ENOMEM; while (1) { int i, j; br = ntfs_attr_pread(vol->mftbmp_na, total, NTFS_BLOCK_SIZE, buf); if (!br) break; total += br; for (i = 0; i < NTFS_BLOCK_SIZE; i++) for (j = 0; j < 8; j++) if (!((buf[i] >> j) & 1)) nr_free++; } free(buf); if (!total) return -errno; ctx->free_mft = nr_free; ctx->state &= ~(NF_FreeMFTOutdate); return nr_free; } static long ntfs_fuse_get_nr_free_clusters(ntfs_volume *vol) { u8 *buf; long nr_free = 0; s64 br, total = 0; if (!(ctx->state & NF_FreeClustersOutdate)) return ctx->free_clusters; buf = malloc(NTFS_BUF_SIZE); if (!buf) return -ENOMEM; while (1) { int i, j; br = ntfs_attr_pread(vol->lcnbmp_na, total, NTFS_BLOCK_SIZE, buf); if (!br) break; total += br; for (i = 0; i < NTFS_BLOCK_SIZE; i++) for (j = 0; j < 8; j++) if (!((buf[i] >> j) & 1)) nr_free++; } free(buf); if (!total) return -errno; ctx->free_clusters = nr_free; ctx->state &= ~(NF_FreeClustersOutdate); return nr_free; } /** * ntfs_fuse_statfs - return information about mounted NTFS volume * @path: ignored (but fuse requires it) * @sfs: statfs structure in which to return the information * * Return information about the mounted NTFS volume @sb in the statfs structure * pointed to by @sfs (this is initialized with zeros before ntfs_statfs is * called). We interpret the values to be correct of the moment in time at * which we are called. Most values are variable otherwise and this isn't just * the free values but the totals as well. For example we can increase the * total number of file nodes if we run out and we can keep doing this until * there is no more space on the volume left at all. * * This code based on ntfs_statfs from ntfs kernel driver. * * Return 0 on success or -errno on error. */ static int ntfs_fuse_statfs(const char *path __attribute__((unused)), struct statfs *sfs) { long size; ntfs_volume *vol; vol = ctx->vol; if (!vol) return -ENODEV; /* Type of filesystem. */ sfs->f_type = NTFS_SB_MAGIC; /* Optimal transfer block size. */ sfs->f_bsize = vol->cluster_size; /* * Total data blocks in file system in units of f_bsize and since * inodes are also stored in data blocs ($MFT is a file) this is just * the total clusters. */ sfs->f_blocks = vol->nr_clusters; /* Free data blocks in file system in units of f_bsize. */ size = ntfs_fuse_get_nr_free_clusters(vol); if (size < 0) size = 0; /* Free blocks avail to non-superuser, same as above on NTFS. */ sfs->f_bavail = sfs->f_bfree = size; /* Number of inodes in file system (at this point in time). */ sfs->f_files = vol->mft_na->data_size >> vol->mft_record_size_bits; /* Free inodes in fs (based on current total count). */ size = ntfs_fuse_get_nr_free_mft_records(vol); if (size < 0) size = 0; sfs->f_ffree = size; /* Maximum length of filenames. */ sfs->f_namelen = NTFS_MAX_NAME_LEN; return 0; } /** * ntfs_fuse_parse_path - split path to path and stream name. * @org_path: path to split * @path: pointer to buffer in which parsed path saved * @stream_name: pointer to buffer where stream name in unicode saved * * This function allocates buffers for @*path and @*stream, user must free them * after use. * * Return values: * <0 Error occurred, return -errno; * 0 No stream name, @*stream is not allocated and set to AT_UNNAMED. * >0 Stream name length in unicode characters. */ static int ntfs_fuse_parse_path(const char *org_path, char **path, ntfschar **stream_name) { char *stream_name_mbs; int res; stream_name_mbs = strdup(org_path); if (!stream_name_mbs) return -errno; *path = strsep(&stream_name_mbs, ":"); if (stream_name_mbs) { *stream_name = NULL; res = ntfs_mbstoucs(stream_name_mbs, stream_name, 0); if (res < 0) return -errno; return res; } *stream_name = AT_UNNAMED; return 0; } static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) { int res = 0; ntfs_inode *ni; ntfs_attr *na; ntfs_volume *vol; char *path = NULL; ntfschar *stream_name; int stream_name_len; vol = ctx->vol; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; memset(stbuf, 0, sizeof(struct stat)); if ((ni = ntfs_pathname_to_inode(vol, NULL, path))) { if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY && !stream_name_len) { stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, I30, 4); if (na) { stbuf->st_size = na->data_size; stbuf->st_blocks = na->allocated_size >> vol->sector_size_bits; ntfs_attr_close(na); } else { stbuf->st_size = 0; stbuf->st_blocks = 0; } stbuf->st_nlink = 1; /* Needed for correct find work. */ } else { stbuf->st_mode = S_IFREG | (0777 & ~ctx->fmask); na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (na) { stbuf->st_size = na->data_size; stbuf->st_blocks = na->allocated_size >> vol->sector_size_bits; ntfs_attr_close(na); } else { stbuf->st_size = 0; stbuf->st_blocks = 0; if (stream_name_len) res = -ENOENT; } stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); } stbuf->st_uid = ctx->uid; stbuf->st_gid = ctx->gid; stbuf->st_ino = ni->mft_no; stbuf->st_atime = ni->last_access_time; stbuf->st_ctime = ni->last_mft_change_time; stbuf->st_mtime = ni->last_data_change_time; ntfs_inode_close(ni); } else res = -ENOENT; free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, const ntfschar *name, const int name_len, const int name_type, const s64 pos __attribute__((unused)), const MFT_REF mref, const unsigned dt_type __attribute__((unused))) { char *filename = NULL; if (name_type == FILE_NAME_DOS) return 0; if (ntfs_ucstombs(name, name_len, &filename, 0) < 0) { Eprintf("Skipping unrepresentable file (inode %lld): %s\n", MREF(mref), strerror(errno)); return 0; } if (MREF(mref) >= FILE_first_user || ctx->show_sys_files) fill_ctx->filler(fill_ctx->buf, filename, NULL, 0); free(filename); return 0; } static int ntfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset __attribute__((unused)), struct fuse_file_info *fi __attribute__((unused))) { ntfs_fuse_fill_context_t fill_ctx; ntfs_volume *vol; ntfs_inode *ni; s64 pos = 0; int err = 0; vol = ctx->vol; fill_ctx.filler = filler; fill_ctx.buf = buf; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) return -errno; if (ntfs_readdir(ni, &pos, &fill_ctx, (ntfs_filldir_t)ntfs_fuse_filler)) err = -errno; ntfs_inode_close(ni); return err; } static int ntfs_fuse_open(const char *org_path, struct fuse_file_info *fi __attribute__((unused))) { ntfs_volume *vol; ntfs_inode *ni; ntfs_attr *na; int res = 0; char *path = NULL; ntfschar *stream_name; int stream_name_len; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; vol = ctx->vol; ni = ntfs_pathname_to_inode(vol, NULL, path); if (ni) { na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (na) { if (NAttrEncrypted(na)) res = -EACCES; ntfs_attr_close(na); } else res = -errno; ntfs_inode_close(ni); } else res = -errno; free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_read(const char *org_path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi __attribute__((unused))) { ntfs_volume *vol; ntfs_inode *ni = NULL; ntfs_attr *na = NULL; char *path = NULL; ntfschar *stream_name; int stream_name_len, res, total = 0; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; vol = ctx->vol; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) { res = -errno; goto exit; } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; goto exit; } if (offset + size > na->data_size) size = na->data_size - offset; while (size) { res = ntfs_attr_pread(na, offset, size, buf); if (res < (s64)size) Eprintf("ntfs_attr_pread returned less bytes than " "requested.\n"); if (res <= 0) { res = -errno; goto exit; } size -= res; offset += res; total += res; } res = total; exit: if (na) ntfs_attr_close(na); if (ni && ntfs_inode_close(ni)) perror("Failed to close inode"); free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_write(const char *org_path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi __attribute__((unused))) { ntfs_volume *vol; ntfs_inode *ni = NULL; ntfs_attr *na = NULL; char *path = NULL; ntfschar *stream_name; int stream_name_len, res, total = 0; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; vol = ctx->vol; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) { res = -errno; goto exit; } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; goto exit; } while (size) { res = ntfs_attr_pwrite(na, offset, size, buf); if (res < (s64)size) Eprintf("ntfs_attr_pwrite returned less bytes than " "requested.\n"); if (res <= 0) { res = -errno; goto exit; } size -= res; offset += res; total += res; } res = total; exit: ctx->state |= (NF_FreeClustersOutdate | NF_FreeMFTOutdate); if (na) ntfs_attr_close(na); if (ni && ntfs_inode_close(ni)) perror("Failed to close inode"); free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_truncate(const char *org_path, off_t size) { ntfs_volume *vol; ntfs_inode *ni = NULL; ntfs_attr *na; int res; char *path = NULL; ntfschar *stream_name; int stream_name_len; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; vol = ctx->vol; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) { res = -errno; goto exit; } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; goto exit; } res = ntfs_attr_truncate(na, size); ctx->state |= (NF_FreeClustersOutdate | NF_FreeMFTOutdate); ntfs_attr_close(na); exit: if (ni && ntfs_inode_close(ni)) perror("Failed to close inode"); free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_chmod(const char *path __attribute__((unused)), mode_t mode __attribute__((unused))) { if (ctx->succeed_chmod) return 0; return -EOPNOTSUPP; } static int ntfs_fuse_create(const char *org_path, const unsigned type) { char *name; ntfschar *uname = NULL; ntfs_inode *dir_ni = NULL, *ni; char *path; int res = 0, uname_len; path = strdup(org_path); if (!path) return -errno; /* Generate unicode filename. */ name = strrchr(path, '/'); name++; uname_len = ntfs_mbstoucs(name, &uname, 0); if (uname_len < 0) { res = -errno; goto exit; } /* Open parent directory. */ *name = 0; dir_ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!dir_ni) { res = -errno; if (res == -ENOENT) res = -EIO; goto exit; } /* Create object specified in @type. */ ni = ntfs_create(dir_ni, uname, uname_len, type); if (ni) ntfs_inode_close(ni); else res = -errno; exit: if (uname) free(uname); if (dir_ni) ntfs_inode_close(dir_ni); free(path); return res; } static int ntfs_fuse_create_stream(const char *path, ntfschar *stream_name, const int stream_name_len) { ntfs_inode *ni; int res = 0; ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!ni) { res = -errno; if (res == -ENOENT) { /* * If such file does not exist, create it and try once * again to add stream to it. */ res = ntfs_fuse_create(path, NTFS_DT_REG); if (!res) return ntfs_fuse_create_stream(path, stream_name, stream_name_len); else res = -errno; } return res; } if (ntfs_attr_add(ni, AT_DATA, stream_name, stream_name_len, NULL, 0)) res = -errno; if (ntfs_inode_close(ni)) perror("Failed to close inode"); return res; } static int ntfs_fuse_mknod(const char *org_path, mode_t mode, dev_t dev __attribute__((unused))) { char *path = NULL; ntfschar *stream_name; int stream_name_len; int res = 0; if (mode && !(mode & S_IFREG)) return -EOPNOTSUPP; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; if (!stream_name_len) res = ntfs_fuse_create(path, NTFS_DT_REG); else res = ntfs_fuse_create_stream(path, stream_name, stream_name_len); free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_rm(const char *org_path) { char *name; ntfschar *uname = NULL; ntfs_inode *dir_ni = NULL, *ni; char *path; int res = 0, uname_len; path = strdup(org_path); if (!path) return -errno; /* Open object for delete. */ ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!ni) { res = -errno; goto exit; } /* Generate unicode filename. */ name = strrchr(path, '/'); name++; uname_len = ntfs_mbstoucs(name, &uname, 0); if (uname_len < 0) { res = -errno; goto exit; } /* Open parent directory. */ *name = 0; dir_ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!dir_ni) { res = -errno; if (res == -ENOENT) res = -EIO; goto exit; } /* Delete object. */ if (ntfs_delete(ni, dir_ni, uname, uname_len)) res = -errno; ni = NULL; exit: if (ni) ntfs_inode_close(ni); if (uname) free(uname); if (dir_ni) ntfs_inode_close(dir_ni); free(path); return res; } static int ntfs_fuse_rm_stream(const char *path, ntfschar *stream_name, const int stream_name_len) { ntfs_inode *ni; ntfs_attr *na; int res = 0; ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!ni) return -errno; na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; goto exit; } if (ntfs_attr_rm(na)) { res = -errno; ntfs_attr_close(na); } exit: if (ntfs_inode_close(ni)) perror("Failed to close inode"); return res; } static int ntfs_fuse_unlink(const char *org_path) { char *path = NULL; ntfschar *stream_name; int stream_name_len; int res = 0; stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); if (stream_name_len < 0) return stream_name_len; if (!stream_name_len) res = ntfs_fuse_rm(path); else res = ntfs_fuse_rm_stream(path, stream_name, stream_name_len); free(path); if (stream_name_len) free(stream_name); return res; } static int ntfs_fuse_mkdir(const char *path, mode_t mode __attribute__((unused))) { return ntfs_fuse_create(path, NTFS_DT_DIR); } static int ntfs_fuse_rmdir(const char *path) { return ntfs_fuse_rm(path); } static int ntfs_fuse_utime(const char *path, struct utimbuf *buf) { ntfs_inode *ni; if (strchr(path, ':')) return 0; /* Unable to change time for named data streams. */ ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!ni) return -errno; if (buf) { ni->last_access_time = buf->actime; ni->last_data_change_time = buf->modtime; ni->last_mft_change_time = buf->modtime; } else { time_t now; now = time(NULL); ni->last_access_time = now; ni->last_data_change_time = now; ni->last_mft_change_time = now; } NInoFileNameSetDirty(ni); NInoSetDirty(ni); if (ntfs_inode_close(ni)) perror("Failed to close inode"); return 0; } #ifdef HAVE_SETXATTR static int ntfs_fuse_getxattr(const char *path, const char *name, char *value, size_t size) { ntfs_attr_search_ctx *actx = NULL; ntfs_volume *vol; ntfs_inode *ni; char *to = value; int ret = 0; if (strcmp(name, "ntfs.streams.list")) return -EOPNOTSUPP; vol = ctx->vol; if (!vol) return -ENODEV; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) return -errno; actx = ntfs_attr_get_search_ctx(ni, NULL); if (!actx) { ret = -errno; ntfs_inode_close(ni); goto exit; } while (!ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE, 0, NULL, 0, actx)) { char *tmp_name = NULL; int tmp_name_len; if (!actx->attr->name_length) continue; tmp_name_len = ntfs_ucstombs((ntfschar *)((u8*)actx->attr + le16_to_cpu(actx->attr->name_offset)), actx->attr->name_length, &tmp_name, 0); if (tmp_name_len < 0) { ret = -errno; goto exit; } if (ret) ret++; /* For space delimiter. */ ret += tmp_name_len; if ((size_t)ret <= size) { /* Don't add space to the beginning of line. */ if (to != value) { *to = ' '; to++; } strncpy(to, tmp_name, tmp_name_len); to += tmp_name_len; } free(tmp_name); } if (errno != ENOENT) ret = -errno; exit: if (actx) ntfs_attr_put_search_ctx(actx); ntfs_inode_close(ni); return ret; } #if 0 /* If this will be enabled, need to fix bug before. (bug description below) */ static const char nf_ns_streams[] = "user.stream."; static const int nf_ns_streams_len = 12; static const char nf_ns_eas[] = "user.ea."; static const int nf_ns_eas_len = 8; static int ntfs_fuse_listxattr(const char *path, char *list, size_t size) { ntfs_attr_search_ctx *actx = NULL; ntfs_volume *vol; ntfs_inode *ni; char *to = list; int ret = 0; vol = ctx->vol; if (!vol) return -ENODEV; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) return -errno; actx = ntfs_attr_get_search_ctx(ni, NULL); if (!actx) { ret = -errno; ntfs_inode_close(ni); goto exit; } while (!ntfs_attr_lookup(AT_DATA, NULL, 0, CASE_SENSITIVE, 0, NULL, 0, actx)) { if (!actx->attr->name_length) continue; ret += actx->attr->name_length + nf_ns_streams_len + 1; if (size && (size_t)ret <= size) { strcpy(to, nf_ns_streams); to += nf_ns_streams_len; /* * BUG: destination buffer length can be bigger than * actx->attr->name_length + 1. (eg. internatinal * characters in utf8) */ if (ntfs_ucstombs((ntfschar *)((u8*)actx->attr + le16_to_cpu(actx->attr->name_offset)), actx->attr->name_length, &to, actx->attr->name_length + 1) < 0) { ret = -errno; goto exit; } to += actx->attr->name_length + 1; } } if (errno != ENOENT) ret = -errno; exit: if (actx) ntfs_attr_put_search_ctx(actx); ntfs_inode_close(ni); fprintf(stderr, "return %d\n", ret); return ret; } static int ntfs_fuse_getxattr(const char *path, const char *name, char *value, size_t size) { ntfs_volume *vol; ntfs_inode *ni; ntfs_attr *na = NULL; int res; ntfschar *lename = NULL; if (strncmp(name, nf_ns_streams, nf_ns_streams_len) || strlen(name) == (size_t)nf_ns_streams_len) return -ENODATA; vol = ctx->vol; if (!vol) return -ENODEV; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) return -errno; if (ntfs_mbstoucs(name + nf_ns_streams_len, &lename, 0) == -1) { res = -errno; goto exit; } na = ntfs_attr_open(ni, AT_DATA, lename, strlen(name) - nf_ns_streams_len); if (!na) { res = -ENODATA; goto exit; } if (size) { if (size >= na->data_size) { res = ntfs_attr_pread(na, 0, na->data_size, value); if (res != na->data_size) res = -errno; } else res = -ERANGE; } else res = na->data_size; exit: if (na) ntfs_attr_close(na); if (lename) free(lename); if (ntfs_inode_close(ni)) perror("Failed to close inode"); return res; } static int ntfs_fuse_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { ntfs_volume *vol; ntfs_inode *ni; ntfs_attr *na = NULL; int res; ntfschar *lename = NULL; if (strncmp(name, nf_ns_streams, nf_ns_streams_len) || strlen(name) == (size_t)nf_ns_streams_len) return -EACCES; vol = ctx->vol; if (!vol) return -ENODEV; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) return -errno; if (ntfs_mbstoucs(name + nf_ns_streams_len, &lename, 0) == -1) { res = -errno; goto exit; } na = ntfs_attr_open(ni, AT_DATA, lename, strlen(name) - nf_ns_streams_len); if (na && flags == XATTR_CREATE) { res = -EEXIST; goto exit; } if (!na) { if (flags == XATTR_REPLACE) { res = -ENODATA; goto exit; } na = ntfs_attr_add(ni, AT_DATA, lename, strlen(name) - nf_ns_streams_len, 0); if (!na) { res = -errno; goto exit; } } res = ntfs_attr_pwrite(na, 0, size, value); if (res != (s64) size) res = -errno; else res = 0; exit: if (na) ntfs_attr_close(na); if (lename) free(lename); if (ntfs_inode_close(ni)) perror("Failed to close inode"); return res; } static int ntfs_fuse_removexattr(const char *path, const char *name) { ntfs_volume *vol; ntfs_inode *ni; ntfs_attr *na = NULL; int res = 0; ntfschar *lename = NULL; if (strncmp(name, nf_ns_streams, nf_ns_streams_len) || strlen(name) == (size_t)nf_ns_streams_len) return -ENODATA; vol = ctx->vol; if (!vol) return -ENODEV; ni = ntfs_pathname_to_inode(vol, NULL, path); if (!ni) return -errno; if (ntfs_mbstoucs(name + nf_ns_streams_len, &lename, 0) == -1) { res = -errno; goto exit; } na = ntfs_attr_open(ni, AT_DATA, lename, strlen(name) - nf_ns_streams_len); if (!na) { res = -ENODATA; goto exit; } if (ntfs_attr_rm(na)) res = -errno; else na = NULL; exit: if (na) ntfs_attr_close(na); if (lename) free(lename); if (ntfs_inode_close(ni)) perror("Failed to close inode"); return res; } #endif /* 0 */ #endif /* HAVE_SETXATTR */ static struct fuse_operations ntfs_fuse_oper = { .getattr = ntfs_fuse_getattr, .readdir = ntfs_fuse_readdir, .open = ntfs_fuse_open, .read = ntfs_fuse_read, .write = ntfs_fuse_write, .truncate = ntfs_fuse_truncate, .statfs = ntfs_fuse_statfs, .chmod = ntfs_fuse_chmod, .mknod = ntfs_fuse_mknod, .unlink = ntfs_fuse_unlink, .mkdir = ntfs_fuse_mkdir, .rmdir = ntfs_fuse_rmdir, .utime = ntfs_fuse_utime, #ifdef HAVE_SETXATTR .getxattr = ntfs_fuse_getxattr, #if 0 .setxattr = ntfs_fuse_setxattr, .removexattr = ntfs_fuse_removexattr, .listxattr = ntfs_fuse_listxattr, #endif /* 0 */ #endif /* HAVE_SETXATTR */ }; static int ntfs_fuse_init(void) { ctx = malloc(sizeof(ntfs_fuse_context_t)); if (!ctx) { perror("malloc failed"); return -1; } *ctx = (ntfs_fuse_context_t) { .state = NF_FreeClustersOutdate | NF_FreeMFTOutdate, .uid = geteuid(), .gid = getegid(), .fmask = 0177, .dmask = 0077, }; return 0; } static int ntfs_fuse_mount(const char *device) { ntfs_volume *vol; vol = utils_mount_volume(device, (ctx->ro) ? MS_RDONLY : 0, ctx->force); if (!vol) { Eprintf("Mount failed.\n"); return -1; } ctx->vol = vol; return 0; } static void ntfs_fuse_destroy(void) { if (ctx->vol) { printf("Unmounting: %s\n", ctx->vol->vol_name); if (ntfs_umount(ctx->vol, FALSE)) perror("Failed to unmount volume"); } free(ctx); } static void signal_handler(int arg __attribute__((unused))) { fuse_exit((fuse_get_context())->fuse); } static char *parse_mount_options(char *org_options, char **device) { char *options, *s, *opt, *val, *ret; BOOL no_def_opts = FALSE; *device = NULL; /* * +3 for different in length of "fsname=..." and "dev=...". * +1 for comma. * +1 for null-terminator. * +PATH_MAX for resolved by realpath() device name */ ret = malloc(strlen(def_opts) + strlen(org_options) + 5 + PATH_MAX); if (!ret) { perror("malloc failed"); return NULL; } *ret = 0; options = strdup(org_options); if (!options) { perror("strdump failed"); return NULL; } s = options; while ((val = strsep(&s, ","))) { opt = strsep(&val, "="); if (!strcmp(opt, "dev")) { /* Device to mount. */ if (!val) { Eprintf("'dev' option should have value.\n"); goto err_exit; } *device = malloc(PATH_MAX + 1); if (!*device) goto err_exit; /* We don't want relative path in /etc/mtab. */ if (val[0] != '/') { if (!realpath(val, *device)) { perror(""); free(*device); *device = NULL; goto err_exit; } } else strcpy(*device, val); } else if (!strcmp(opt, "ro")) { /* Read-only mount. */ if (val) { Eprintf("'ro' option should not have value.\n"); goto err_exit; } ctx->ro =TRUE; strcat(ret, "ro,"); #ifdef DEBUG } else if (!strcmp(opt, "fake_ro")) { if (val) { Eprintf("'fake_ro' option should not have " "value.\n"); goto err_exit; } ctx->ro =TRUE; #endif } else if (!strcmp(opt, "fsname")) { /* Filesystem name. */ /* * We need this to be able to check whether filesystem * mounted or not. */ Eprintf("'fsname' is unsupported option.\n"); goto err_exit; } else if (!strcmp(opt, "no_def_opts")) { if (val) { Eprintf("'no_def_opts' option should not have " "value.\n"); goto err_exit; } no_def_opts = TRUE; /* Don't add default options. */ } else if (!strcmp(opt, "umask")) { if (!val) { Eprintf("'umask' option should have value.\n"); goto err_exit; } sscanf(val, "%i", &ctx->fmask); ctx->dmask = ctx->fmask; } else if (!strcmp(opt, "fmask")) { if (!val) { Eprintf("'fmask' option should have value.\n"); goto err_exit; } sscanf(val, "%i", &ctx->fmask); } else if (!strcmp(opt, "dmask")) { if (!val) { Eprintf("'dmask' option should have value.\n"); goto err_exit; } sscanf(val, "%i", &ctx->dmask); } else if (!strcmp(opt, "uid")) { if (!val) { Eprintf("'uid' option should have value.\n"); goto err_exit; } sscanf(val, "%i", &ctx->uid); } else if (!strcmp(opt, "gid")) { if (!val) { Eprintf("'gid' option should have value.\n"); goto err_exit; } sscanf(val, "%i", &ctx->gid); } else if (!strcmp(opt, "show_sys_files")) { if (val) { Eprintf("'show_sys_files' option should not " "have value.\n"); goto err_exit; } ctx->show_sys_files = TRUE; } else if (!strcmp(opt, "succeed_chmod")) { if (val) { Eprintf("'succeed_chmod' option should not " "have value.\n"); goto err_exit; } ctx->succeed_chmod = TRUE; } else if (!strcmp(opt, "force")) { if (val) { Eprintf("'force' option should not " "have value.\n"); goto err_exit; } ctx->force = TRUE; } else { /* Probably FUSE option. */ strcat(ret, opt); if (val) { strcat(ret, "="); strcat(ret, val); } strcat(ret, ","); } } if (!*device) goto err_exit; if (!no_def_opts) strcat(ret, def_opts); strcat(ret, "fsname="); strcat(ret, *device); exit: free(options); return ret; err_exit: free(ret); ret = NULL; goto exit; } static void usage(void) { Eprintf("\n%s v%s - NTFS module for FUSE.\n\n", EXEC_NAME, VERSION); Eprintf("Copyright (c) 2005 Yura Pakhuchiy\n\n"); Eprintf("usage: %s mount_point -o dev=device[,other_options]\n\n", EXEC_NAME); Eprintf("Possible options are:\n\tdefault_permissions\n\tallow_other\n" "\tkernel_cache\n\tlarge_read\n\tdirect_io\n\tmax_read\n\t" "force\n\tro\n\tno_def_opts\n\tumask\n\tfmask\n\tdmask\n\t" "uid\n\tgid\n\tshow_sys_files\n\tsucceed_chmod\n\tdev\n\n"); Eprintf("Default options are: \"%s\".\n", def_opts); } /** * parse_options - Read and validate the programs command line * * Read the command line, verify the syntax and parse the options. * This function is very long, but quite simple. * * Return: 1 Success * 0 Error, one or more problems */ static int parse_options (int argc, char *argv[]) { int err = 0, help = 0; char c = -1; static const char *sopt = "-o:h?qv"; static const struct option lopt[] = { { "options", required_argument, NULL, 'o' }, { "help", no_argument, NULL, 'h' }, { "quiet", no_argument, NULL, 'q' }, { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; opterr = 0; /* We'll handle the errors, thank you. */ opts.mnt_point = NULL; opts.options = NULL; while ((c = getopt_long (argc, argv, sopt, lopt, NULL)) != (char)-1) { switch (c) { case 1: /* A non-option argument */ if (!opts.mnt_point) opts.mnt_point = argv[optind - 1]; else { Eprintf("You must specify exactly one " "mount point.\n"); err++; } break; case 'o': if (!opts.options) opts.options = argv[optind - 1]; else { Eprintf("You must specify exactly one " "set of options.\n"); err++; } break; case 'h': case '?': help++; break; case 'q': opts.quiet++; break; case 'v': opts.verbose++; break; default: Eprintf("Unknown option '%s'.\n", argv[optind-1]); err++; break; } } if (help) { opts.quiet = 0; } else { if (!opts.options) { Eprintf("No mount options passed, but 'dev' option is " "mandatory.\n"); err++; } if (!opts.mnt_point) { if (argc > 1) Eprintf("No mount point specified.\n"); err++; } if (opts.quiet && opts.verbose) { Eprintf("You may not use --quiet and --verbose at " "the same time.\n"); err++; } } if (help || err) usage(); return (!help && !err); } int main(int argc, char *argv[]) { char *parsed_options, *device; struct fuse *fh; int ffd; setlocale(LC_ALL, ""); signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); if (!parse_options(argc, argv)) return 1; ntfs_fuse_init(); /* Parse options. */ parsed_options = parse_mount_options(opts.options, &device); if (!device) { Eprintf("'dev' option is mandatory.\n"); ntfs_fuse_destroy(); return 2; } if (!parsed_options) { free(device); ntfs_fuse_destroy(); return 3; } /* Mount volume. */ if (ntfs_fuse_mount(device)) { free(device); ntfs_fuse_destroy(); return 4; } free(device); /* Create filesystem. */ ffd = fuse_mount(opts.mnt_point, parsed_options); if (ffd == -1) { Eprintf("fuse_mount failed.\n"); ntfs_fuse_destroy(); return 5; } free(parsed_options); #ifndef DEBUG fh = fuse_new(ffd, "use_ino", &ntfs_fuse_oper, sizeof(ntfs_fuse_oper)); #else fh = fuse_new(ffd, "debug,use_ino", &ntfs_fuse_oper, sizeof(ntfs_fuse_oper)); #endif if (!fh) { Eprintf("fuse_new failed.\n"); close(ffd); fuse_unmount(opts.mnt_point); ntfs_fuse_destroy(); return 6; } #ifndef DEBUG if (daemon(0, 0)) Eprintf("Failed to daemonize.\n"); #endif printf("Mounted: %s\n", ctx->vol->vol_name); /* Main loop. */ if (fuse_loop(fh)) Eprintf("fuse_loop failed.\n"); /* Destroy. */ fuse_destroy(fh); close(ffd); fuse_unmount(opts.mnt_point); ntfs_fuse_destroy(); return 0; }