| /** |
| * ntfs-3g - Third Generation NTFS Driver |
| * |
| * Copyright (c) 2005-2007 Yura Pakhuchiy |
| * Copyright (c) 2005 Yuval Fledel |
| * Copyright (c) 2006-2009 Szabolcs Szakacsits |
| * Copyright (c) 2007-2015 Jean-Pierre Andre |
| * Copyright (c) 2009 Erik Larsson |
| * |
| * This file is originated from the Linux-NTFS project. |
| * |
| * 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 NTFS-3G |
| * 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 <fuse.h> |
| #include <fuse_lowlevel.h> |
| |
| #if !defined(FUSE_VERSION) || (FUSE_VERSION < 26) |
| #error "***********************************************************" |
| #error "* *" |
| #error "* Compilation requires at least FUSE version 2.6.0! *" |
| #error "* *" |
| #error "***********************************************************" |
| #endif |
| |
| #ifdef HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #ifdef HAVE_LOCALE_H |
| #include <locale.h> |
| #endif |
| #include <signal.h> |
| #ifdef HAVE_LIMITS_H |
| #include <limits.h> |
| #endif |
| #include <syslog.h> |
| #include <sys/wait.h> |
| |
| #ifdef HAVE_SETXATTR |
| #include <sys/xattr.h> |
| #endif |
| |
| #ifdef HAVE_SYS_TYPES_H |
| #include <sys/types.h> |
| #endif |
| #ifdef HAVE_SYS_MKDEV_H |
| #include <sys/mkdev.h> |
| #endif |
| |
| #if defined(__APPLE__) || defined(__DARWIN__) |
| #include <sys/dirent.h> |
| #elif defined(__sun) && defined (__SVR4) |
| #include <sys/param.h> |
| #endif /* defined(__APPLE__) || defined(__DARWIN__), ... */ |
| |
| #ifdef HAVE_LINUX_FS_H |
| #include <linux/fs.h> |
| #endif |
| |
| #include "compat.h" |
| #include "bitmap.h" |
| #include "attrib.h" |
| #include "inode.h" |
| #include "volume.h" |
| #include "dir.h" |
| #include "unistr.h" |
| #include "layout.h" |
| #include "index.h" |
| #include "ntfstime.h" |
| #include "security.h" |
| #include "reparse.h" |
| #include "object_id.h" |
| #include "efs.h" |
| #include "logging.h" |
| #include "xattrs.h" |
| #include "misc.h" |
| #include "ioctl.h" |
| |
| #include "ntfs-3g_common.h" |
| |
| /* |
| * The following permission checking modes are governed by |
| * the LPERMSCONFIG value in param.h |
| */ |
| |
| /* ACLS may be checked by kernel (requires a fuse patch) or here */ |
| #define KERNELACLS ((LPERMSCONFIG > 6) & (LPERMSCONFIG < 10)) |
| /* basic permissions may be checked by kernel or here */ |
| #define KERNELPERMS (((LPERMSCONFIG - 1) % 6) < 3) |
| /* may want to use fuse/kernel cacheing */ |
| #define CACHEING (!(LPERMSCONFIG % 3)) |
| |
| #if KERNELACLS & !KERNELPERMS |
| #error "Incompatible options KERNELACLS and KERNELPERMS" |
| #endif |
| |
| #if !CACHEING |
| #define ATTR_TIMEOUT 0.0 |
| #define ENTRY_TIMEOUT 0.0 |
| #else |
| #if defined(__sun) && defined (__SVR4) |
| #define ATTR_TIMEOUT 10.0 |
| #define ENTRY_TIMEOUT 10.0 |
| #else /* defined(__sun) && defined (__SVR4) */ |
| /* |
| * FUSE cacheing is only usable with basic permissions |
| * checked by the kernel with external fuse >= 2.8 |
| */ |
| #if KERNELACLS | !KERNELPERMS |
| #warning "Fuse cacheing is only usable with basic permissions checked by kernel" |
| #endif |
| #define ATTR_TIMEOUT (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 1.0 : 0.0) |
| #define ENTRY_TIMEOUT (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 1.0 : 0.0) |
| #endif /* defined(__sun) && defined (__SVR4) */ |
| #endif |
| #define GHOSTLTH 40 /* max length of a ghost file name - see ghostformat */ |
| |
| /* sometimes the kernel cannot check access */ |
| #define ntfs_real_allowed_access(scx, ni, type) ntfs_allowed_access(scx, ni, type) |
| #if POSIXACLS & KERNELPERMS & !KERNELACLS |
| /* short-circuit if PERMS checked by kernel and ACLs by fs */ |
| #define ntfs_allowed_access(scx, ni, type) \ |
| ((scx)->vol->secure_flags & (1 << SECURITY_DEFAULT) \ |
| ? 1 : ntfs_allowed_access(scx, ni, type)) |
| #endif |
| |
| #define set_archive(ni) (ni)->flags |= FILE_ATTR_ARCHIVE |
| #define INODE(ino) ((ino) == 1 ? (MFT_REF)FILE_root : (MFT_REF)(ino)) |
| |
| typedef enum { |
| FSTYPE_NONE, |
| FSTYPE_UNKNOWN, |
| FSTYPE_FUSE, |
| FSTYPE_FUSEBLK |
| } fuse_fstype; |
| |
| typedef struct fill_item { |
| struct fill_item *next; |
| size_t bufsize; |
| size_t off; |
| char buf[0]; |
| } ntfs_fuse_fill_item_t; |
| |
| typedef struct fill_context { |
| struct fill_item *first; |
| struct fill_item *last; |
| off_t off; |
| fuse_req_t req; |
| fuse_ino_t ino; |
| BOOL filled; |
| } ntfs_fuse_fill_context_t; |
| |
| struct open_file { |
| struct open_file *next; |
| struct open_file *previous; |
| long long ghost; |
| fuse_ino_t ino; |
| fuse_ino_t parent; |
| int state; |
| } ; |
| |
| enum { |
| CLOSE_GHOST = 1, |
| CLOSE_COMPRESSED = 2, |
| CLOSE_ENCRYPTED = 4, |
| CLOSE_DMTIME = 8 |
| }; |
| |
| enum RM_TYPES { |
| RM_LINK, |
| RM_DIR, |
| RM_ANY, |
| } ; |
| |
| static struct ntfs_options opts; |
| |
| const char *EXEC_NAME = "lowntfs-3g"; |
| |
| static ntfs_fuse_context_t *ctx; |
| static u32 ntfs_sequence; |
| static const char ghostformat[] = ".ghost-ntfs-3g-%020llu"; |
| |
| static const char *usage_msg = |
| "\n" |
| "%s %s %s %d - Third Generation NTFS Driver\n" |
| "\t\tConfiguration type %d, " |
| #ifdef HAVE_SETXATTR |
| "XATTRS are on, " |
| #else |
| "XATTRS are off, " |
| #endif |
| #if POSIXACLS |
| "POSIX ACLS are on\n" |
| #else |
| "POSIX ACLS are off\n" |
| #endif |
| "\n" |
| "Copyright (C) 2005-2007 Yura Pakhuchiy\n" |
| "Copyright (C) 2006-2009 Szabolcs Szakacsits\n" |
| "Copyright (C) 2007-2016 Jean-Pierre Andre\n" |
| "Copyright (C) 2009 Erik Larsson\n" |
| "\n" |
| "Usage: %s [-o option[,...]] <device|image_file> <mount_point>\n" |
| "\n" |
| "Options: ro (read-only mount), windows_names, uid=, gid=,\n" |
| " umask=, fmask=, dmask=, streams_interface=.\n" |
| " Please see the details in the manual (type: man ntfs-3g).\n" |
| "\n" |
| "Example: ntfs-3g /dev/sda1 /mnt/windows\n" |
| "\n" |
| "%s"; |
| |
| static const char ntfs_bad_reparse[] = "unsupported reparse point"; |
| |
| #ifdef FUSE_INTERNAL |
| int drop_privs(void); |
| int restore_privs(void); |
| #else |
| /* |
| * setuid and setgid root ntfs-3g denies to start with external FUSE, |
| * therefore the below functions are no-op in such case. |
| */ |
| static int drop_privs(void) { return 0; } |
| #if defined(linux) || defined(__uClinux__) |
| static int restore_privs(void) { return 0; } |
| #endif |
| |
| static const char *setuid_msg = |
| "Mount is denied because setuid and setgid root ntfs-3g is insecure with the\n" |
| "external FUSE library. Either remove the setuid/setgid bit from the binary\n" |
| "or rebuild NTFS-3G with integrated FUSE support and make it setuid root.\n" |
| "Please see more information at\n" |
| "http://tuxera.com/community/ntfs-3g-faq/#unprivileged\n"; |
| |
| static const char *unpriv_fuseblk_msg = |
| "Unprivileged user can not mount NTFS block devices using the external FUSE\n" |
| "library. Either mount the volume as root, or rebuild NTFS-3G with integrated\n" |
| "FUSE support and make it setuid root. Please see more information at\n" |
| "http://tuxera.com/community/ntfs-3g-faq/#unprivileged\n"; |
| #endif |
| |
| |
| static void ntfs_fuse_update_times(ntfs_inode *ni, ntfs_time_update_flags mask) |
| { |
| if (ctx->atime == ATIME_DISABLED) |
| mask &= ~NTFS_UPDATE_ATIME; |
| else if (ctx->atime == ATIME_RELATIVE && mask == NTFS_UPDATE_ATIME && |
| (sle64_to_cpu(ni->last_access_time) |
| >= sle64_to_cpu(ni->last_data_change_time)) && |
| (sle64_to_cpu(ni->last_access_time) |
| >= sle64_to_cpu(ni->last_mft_change_time))) |
| return; |
| ntfs_inode_update_times(ni, mask); |
| } |
| |
| static s64 ntfs_get_nr_free_mft_records(ntfs_volume *vol) |
| { |
| ntfs_attr *na = vol->mftbmp_na; |
| s64 nr_free = ntfs_attr_get_free_bits(na); |
| |
| if (nr_free >= 0) |
| nr_free += (na->allocated_size - na->data_size) << 3; |
| return nr_free; |
| } |
| |
| /* |
| * Fill a security context as needed by security functions |
| * returns TRUE if there is a user mapping, |
| * FALSE if there is none |
| * This is not an error and the context is filled anyway, |
| * it is used for implicit Windows-like inheritance |
| */ |
| |
| static BOOL ntfs_fuse_fill_security_context(fuse_req_t req, |
| struct SECURITY_CONTEXT *scx) |
| { |
| const struct fuse_ctx *fusecontext; |
| |
| scx->vol = ctx->vol; |
| scx->mapping[MAPUSERS] = ctx->security.mapping[MAPUSERS]; |
| scx->mapping[MAPGROUPS] = ctx->security.mapping[MAPGROUPS]; |
| scx->pseccache = &ctx->seccache; |
| if (req) { |
| fusecontext = fuse_req_ctx(req); |
| scx->uid = fusecontext->uid; |
| scx->gid = fusecontext->gid; |
| scx->tid = fusecontext->pid; |
| #ifdef FUSE_CAP_DONT_MASK |
| /* the umask can be processed by the file system */ |
| scx->umask = fusecontext->umask; |
| #else |
| /* the umask if forced by fuse on creation */ |
| scx->umask = 0; |
| #endif |
| |
| } else { |
| scx->uid = 0; |
| scx->gid = 0; |
| scx->tid = 0; |
| scx->umask = 0; |
| } |
| return (ctx->security.mapping[MAPUSERS] != (struct MAPPING*)NULL); |
| } |
| |
| static u64 ntfs_fuse_inode_lookup(fuse_ino_t parent, const char *name) |
| { |
| u64 ino = (u64)-1; |
| u64 inum; |
| ntfs_inode *dir_ni; |
| |
| /* Open target directory. */ |
| dir_ni = ntfs_inode_open(ctx->vol, INODE(parent)); |
| if (dir_ni) { |
| /* Lookup file */ |
| inum = ntfs_inode_lookup_by_mbsname(dir_ni, name); |
| /* never return inodes 0 and 1 */ |
| if (MREF(inum) <= 1) { |
| inum = (u64)-1; |
| errno = ENOENT; |
| } |
| if (ntfs_inode_close(dir_ni) |
| || (inum == (u64)-1)) |
| ino = (u64)-1; |
| else |
| ino = MREF(inum); |
| } |
| return (ino); |
| } |
| |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| |
| /* |
| * Check access to parent directory |
| * |
| * file inode is only opened when not fed in and S_ISVTX is requested, |
| * when already open and S_ISVTX, it *HAS TO* be fed in. |
| * |
| * returns 1 if allowed, |
| * 0 if not allowed or some error occurred (errno tells why) |
| */ |
| |
| static int ntfs_allowed_dir_access(struct SECURITY_CONTEXT *scx, |
| ntfs_inode *dir_ni, fuse_ino_t ino, |
| ntfs_inode *ni, mode_t accesstype) |
| { |
| int allowed; |
| ntfs_inode *ni2; |
| struct stat stbuf; |
| |
| allowed = ntfs_allowed_access(scx, dir_ni, accesstype); |
| /* |
| * for an not-owned sticky directory, have to |
| * check whether file itself is owned |
| */ |
| if ((accesstype == (S_IWRITE + S_IEXEC + S_ISVTX)) |
| && (allowed == 2)) { |
| if (ni) |
| ni2 = ni; |
| else |
| ni2 = ntfs_inode_open(ctx->vol, INODE(ino)); |
| allowed = 0; |
| if (ni2) { |
| allowed = (ntfs_get_owner_mode(scx,ni2,&stbuf) >= 0) |
| && (stbuf.st_uid == scx->uid); |
| if (!ni) |
| ntfs_inode_close(ni2); |
| } |
| } |
| return (allowed); |
| } |
| |
| #endif /* !KERNELPERMS | (POSIXACLS & !KERNELACLS) */ |
| |
| /** |
| * 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. |
| * |
| * Returns 0 on success or -errno on error. |
| */ |
| |
| static void ntfs_fuse_statfs(fuse_req_t req, |
| fuse_ino_t ino __attribute__((unused))) |
| { |
| struct statvfs sfs; |
| s64 size; |
| int delta_bits; |
| ntfs_volume *vol; |
| |
| vol = ctx->vol; |
| if (vol) { |
| /* |
| * File system block size. Used to calculate used/free space by df. |
| * Incorrectly documented as "optimal transfer block size". |
| */ |
| sfs.f_bsize = vol->cluster_size; |
| |
| /* Fundamental file system block size, used as the unit. */ |
| sfs.f_frsize = vol->cluster_size; |
| |
| /* |
| * Total number of blocks on file system in units of f_frsize. |
| * Since inodes are also stored in blocks ($MFT is a file) hence |
| * this is the number of clusters on the volume. |
| */ |
| sfs.f_blocks = vol->nr_clusters; |
| |
| /* Free blocks available for all and for non-privileged processes. */ |
| size = vol->free_clusters; |
| if (size < 0) |
| size = 0; |
| sfs.f_bavail = sfs.f_bfree = size; |
| |
| /* Free inodes on the free space */ |
| delta_bits = vol->cluster_size_bits - vol->mft_record_size_bits; |
| if (delta_bits >= 0) |
| size <<= delta_bits; |
| else |
| size >>= -delta_bits; |
| |
| /* Number of inodes at this point in time. */ |
| sfs.f_files = (vol->mftbmp_na->allocated_size << 3) + size; |
| |
| /* Free inodes available for all and for non-privileged processes. */ |
| size += vol->free_mft_records; |
| if (size < 0) |
| size = 0; |
| sfs.f_ffree = sfs.f_favail = size; |
| |
| /* Maximum length of filenames. */ |
| sfs.f_namemax = NTFS_MAX_NAME_LEN; |
| fuse_reply_statfs(req, &sfs); |
| } else |
| fuse_reply_err(req, ENODEV); |
| |
| } |
| |
| static void set_fuse_error(int *err) |
| { |
| if (!*err) |
| *err = -errno; |
| } |
| |
| #if 0 && (defined(__APPLE__) || defined(__DARWIN__)) /* Unfinished. */ |
| static int ntfs_macfuse_getxtimes(const char *org_path, |
| struct timespec *bkuptime, struct timespec *crtime) |
| { |
| int res = 0; |
| ntfs_inode *ni; |
| 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; |
| memset(bkuptime, 0, sizeof(struct timespec)); |
| memset(crtime, 0, sizeof(struct timespec)); |
| ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| |
| /* We have no backup timestamp in NTFS. */ |
| crtime->tv_sec = ni->creation_time; |
| exit: |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| free(path); |
| if (stream_name_len) |
| free(stream_name); |
| return res; |
| } |
| |
| int ntfs_macfuse_setcrtime(const char *path, const struct timespec *tv) |
| { |
| ntfs_inode *ni; |
| int res = 0; |
| |
| if (ntfs_fuse_is_named_data_stream(path)) |
| return -EINVAL; /* n/a for named data streams. */ |
| ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); |
| if (!ni) |
| return -errno; |
| |
| if (tv) { |
| ni->creation_time = tv->tv_sec; |
| ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); |
| } |
| |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| return res; |
| } |
| |
| int ntfs_macfuse_setbkuptime(const char *path, const struct timespec *tv) |
| { |
| ntfs_inode *ni; |
| int res = 0; |
| |
| if (ntfs_fuse_is_named_data_stream(path)) |
| return -EINVAL; /* n/a for named data streams. */ |
| ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); |
| if (!ni) |
| return -errno; |
| |
| /* |
| * Only pretending to set backup time successfully to please the APIs of |
| * Mac OS X. In reality, NTFS has no backup time. |
| */ |
| |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| return res; |
| } |
| |
| int ntfs_macfuse_setchgtime(const char *path, const struct timespec *tv) |
| { |
| ntfs_inode *ni; |
| int res = 0; |
| |
| if (ntfs_fuse_is_named_data_stream(path)) |
| return -EINVAL; /* n/a for named data streams. */ |
| ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); |
| if (!ni) |
| return -errno; |
| |
| if (tv) { |
| ni->last_mft_change_time = tv->tv_sec; |
| ntfs_fuse_update_times(ni, 0); |
| } |
| |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| return res; |
| } |
| #endif /* defined(__APPLE__) || defined(__DARWIN__) */ |
| |
| static void ntfs_init(void *userdata __attribute__((unused)), |
| struct fuse_conn_info *conn) |
| { |
| #if defined(__APPLE__) || defined(__DARWIN__) |
| FUSE_ENABLE_XTIMES(conn); |
| #endif |
| #ifdef FUSE_CAP_DONT_MASK |
| /* request umask not to be enforced by fuse */ |
| conn->want |= FUSE_CAP_DONT_MASK; |
| #endif /* defined FUSE_CAP_DONT_MASK */ |
| #ifdef FUSE_CAP_BIG_WRITES |
| if (ctx->big_writes |
| && ((ctx->vol->nr_clusters << ctx->vol->cluster_size_bits) |
| >= SAFE_CAPACITY_FOR_BIG_WRITES)) |
| conn->want |= FUSE_CAP_BIG_WRITES; |
| #endif |
| #ifdef FUSE_CAP_IOCTL_DIR |
| conn->want |= FUSE_CAP_IOCTL_DIR; |
| #endif /* defined(FUSE_CAP_IOCTL_DIR) */ |
| } |
| |
| static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, |
| ntfs_inode *ni, struct stat *stbuf) |
| { |
| int res = 0; |
| ntfs_attr *na; |
| BOOL withusermapping; |
| |
| memset(stbuf, 0, sizeof(struct stat)); |
| withusermapping = (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL); |
| if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) |
| || (ni->flags & FILE_ATTR_REPARSE_POINT)) { |
| if (ni->flags & FILE_ATTR_REPARSE_POINT) { |
| char *target; |
| int attr_size; |
| |
| errno = 0; |
| target = ntfs_make_symlink(ni, ctx->abs_mnt_point, |
| &attr_size); |
| /* |
| * If the reparse point is not a valid |
| * directory junction, and there is no error |
| * we still display as a symlink |
| */ |
| if (target || (errno == EOPNOTSUPP)) { |
| /* returning attribute size */ |
| if (target) |
| stbuf->st_size = attr_size; |
| else |
| stbuf->st_size = |
| sizeof(ntfs_bad_reparse); |
| stbuf->st_blocks = |
| (ni->allocated_size + 511) >> 9; |
| stbuf->st_nlink = |
| le16_to_cpu(ni->mrec->link_count); |
| stbuf->st_mode = S_IFLNK; |
| free(target); |
| } else { |
| res = -errno; |
| goto exit; |
| } |
| } else { |
| /* Directory. */ |
| stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); |
| /* get index size, if not known */ |
| if (!test_nino_flag(ni, KnownSize)) { |
| na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, |
| NTFS_INDEX_I30, 4); |
| if (na) { |
| ni->data_size = na->data_size; |
| ni->allocated_size = na->allocated_size; |
| set_nino_flag(ni, KnownSize); |
| ntfs_attr_close(na); |
| } |
| } |
| stbuf->st_size = ni->data_size; |
| stbuf->st_blocks = ni->allocated_size >> 9; |
| stbuf->st_nlink = 1; /* Make find(1) work */ |
| } |
| } else { |
| /* Regular or Interix (INTX) file. */ |
| stbuf->st_mode = S_IFREG; |
| stbuf->st_size = ni->data_size; |
| #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
| /* |
| * return data size rounded to next 512 byte boundary for |
| * encrypted files to include padding required for decryption |
| * also include 2 bytes for padding info |
| */ |
| if (ctx->efs_raw |
| && (ni->flags & FILE_ATTR_ENCRYPTED) |
| && ni->data_size) |
| stbuf->st_size = ((ni->data_size + 511) & ~511) + 2; |
| #endif /* HAVE_SETXATTR */ |
| /* |
| * Temporary fix to make ActiveSync work via Samba 3.0. |
| * See more on the ntfs-3g-devel list. |
| */ |
| stbuf->st_blocks = (ni->allocated_size + 511) >> 9; |
| stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); |
| if (ni->flags & FILE_ATTR_SYSTEM) { |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) { |
| stbuf->st_ino = ni->mft_no; |
| goto nodata; |
| } |
| /* Check whether it's Interix FIFO or socket. */ |
| if (!(ni->flags & FILE_ATTR_HIDDEN)) { |
| /* FIFO. */ |
| if (na->data_size == 0) |
| stbuf->st_mode = S_IFIFO; |
| /* Socket link. */ |
| if (na->data_size == 1) |
| stbuf->st_mode = S_IFSOCK; |
| } |
| /* |
| * Check whether it's Interix symbolic link, block or |
| * character device. |
| */ |
| if ((u64)na->data_size <= sizeof(INTX_FILE_TYPES) |
| + sizeof(ntfschar) * PATH_MAX |
| && (u64)na->data_size > |
| sizeof(INTX_FILE_TYPES)) { |
| INTX_FILE *intx_file; |
| |
| intx_file = |
| (INTX_FILE*)ntfs_malloc(na->data_size); |
| if (!intx_file) { |
| res = -errno; |
| ntfs_attr_close(na); |
| goto exit; |
| } |
| if (ntfs_attr_pread(na, 0, na->data_size, |
| intx_file) != na->data_size) { |
| res = -errno; |
| free(intx_file); |
| ntfs_attr_close(na); |
| goto exit; |
| } |
| if (intx_file->magic == INTX_BLOCK_DEVICE && |
| na->data_size == (s64)offsetof( |
| INTX_FILE, device_end)) { |
| stbuf->st_mode = S_IFBLK; |
| stbuf->st_rdev = makedev(le64_to_cpu( |
| intx_file->major), |
| le64_to_cpu( |
| intx_file->minor)); |
| } |
| if (intx_file->magic == INTX_CHARACTER_DEVICE && |
| na->data_size == (s64)offsetof( |
| INTX_FILE, device_end)) { |
| stbuf->st_mode = S_IFCHR; |
| stbuf->st_rdev = makedev(le64_to_cpu( |
| intx_file->major), |
| le64_to_cpu( |
| intx_file->minor)); |
| } |
| if (intx_file->magic == INTX_SYMBOLIC_LINK) |
| stbuf->st_mode = S_IFLNK; |
| free(intx_file); |
| } |
| ntfs_attr_close(na); |
| } |
| stbuf->st_mode |= (0777 & ~ctx->fmask); |
| } |
| if (withusermapping) { |
| if (ntfs_get_owner_mode(scx,ni,stbuf) < 0) |
| set_fuse_error(&res); |
| } else { |
| stbuf->st_uid = ctx->uid; |
| stbuf->st_gid = ctx->gid; |
| } |
| if (S_ISLNK(stbuf->st_mode)) |
| stbuf->st_mode |= 0777; |
| nodata : |
| stbuf->st_ino = ni->mft_no; |
| #ifdef HAVE_STRUCT_STAT_ST_ATIMESPEC |
| stbuf->st_atimespec = ntfs2timespec(ni->last_access_time); |
| stbuf->st_ctimespec = ntfs2timespec(ni->last_mft_change_time); |
| stbuf->st_mtimespec = ntfs2timespec(ni->last_data_change_time); |
| #elif defined(HAVE_STRUCT_STAT_ST_ATIM) |
| stbuf->st_atim = ntfs2timespec(ni->last_access_time); |
| stbuf->st_ctim = ntfs2timespec(ni->last_mft_change_time); |
| stbuf->st_mtim = ntfs2timespec(ni->last_data_change_time); |
| #elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) |
| { |
| struct timespec ts; |
| |
| ts = ntfs2timespec(ni->last_access_time); |
| stbuf->st_atime = ts.tv_sec; |
| stbuf->st_atimensec = ts.tv_nsec; |
| ts = ntfs2timespec(ni->last_mft_change_time); |
| stbuf->st_ctime = ts.tv_sec; |
| stbuf->st_ctimensec = ts.tv_nsec; |
| ts = ntfs2timespec(ni->last_data_change_time); |
| stbuf->st_mtime = ts.tv_sec; |
| stbuf->st_mtimensec = ts.tv_nsec; |
| } |
| #else |
| #warning "No known way to set nanoseconds in struct stat !" |
| { |
| struct timespec ts; |
| |
| ts = ntfs2timespec(ni->last_access_time); |
| stbuf->st_atime = ts.tv_sec; |
| ts = ntfs2timespec(ni->last_mft_change_time); |
| stbuf->st_ctime = ts.tv_sec; |
| ts = ntfs2timespec(ni->last_data_change_time); |
| stbuf->st_mtime = ts.tv_sec; |
| } |
| #endif |
| exit: |
| return (res); |
| } |
| |
| static void ntfs_fuse_getattr(fuse_req_t req, fuse_ino_t ino, |
| struct fuse_file_info *fi __attribute__((unused))) |
| { |
| int res; |
| ntfs_inode *ni; |
| struct stat stbuf; |
| struct SECURITY_CONTEXT security; |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| res = -errno; |
| else { |
| ntfs_fuse_fill_security_context(req, &security); |
| res = ntfs_fuse_getstat(&security, ni, &stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| if (!res) |
| fuse_reply_attr(req, &stbuf, ATTR_TIMEOUT); |
| else |
| fuse_reply_err(req, -res); |
| } |
| |
| static __inline__ BOOL ntfs_fuse_fillstat(struct SECURITY_CONTEXT *scx, |
| struct fuse_entry_param *pentry, u64 iref) |
| { |
| ntfs_inode *ni; |
| BOOL ok = FALSE; |
| |
| pentry->ino = MREF(iref); |
| ni = ntfs_inode_open(ctx->vol, pentry->ino); |
| if (ni) { |
| if (!ntfs_fuse_getstat(scx, ni, &pentry->attr)) { |
| pentry->generation = 1; |
| pentry->attr_timeout = ATTR_TIMEOUT; |
| pentry->entry_timeout = ENTRY_TIMEOUT; |
| ok = TRUE; |
| } |
| if (ntfs_inode_close(ni)) |
| ok = FALSE; |
| } |
| return (ok); |
| } |
| |
| |
| static void ntfs_fuse_lookup(fuse_req_t req, fuse_ino_t parent, |
| const char *name) |
| { |
| struct SECURITY_CONTEXT security; |
| struct fuse_entry_param entry; |
| ntfs_inode *dir_ni; |
| u64 iref; |
| BOOL ok = FALSE; |
| |
| if (strlen(name) < 256) { |
| dir_ni = ntfs_inode_open(ctx->vol, INODE(parent)); |
| if (dir_ni) { |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| /* |
| * make sure the parent directory is searchable |
| */ |
| if (ntfs_fuse_fill_security_context(req, &security) |
| && !ntfs_allowed_access(&security,dir_ni,S_IEXEC)) { |
| ntfs_inode_close(dir_ni); |
| errno = EACCES; |
| } else { |
| #else |
| ntfs_fuse_fill_security_context(req, &security); |
| #endif |
| iref = ntfs_inode_lookup_by_mbsname(dir_ni, |
| name); |
| /* never return inodes 0 and 1 */ |
| if (MREF(iref) <= 1) { |
| iref = (u64)-1; |
| errno = ENOENT; |
| } |
| ok = !ntfs_inode_close(dir_ni) |
| && (iref != (u64)-1) |
| && ntfs_fuse_fillstat( |
| &security,&entry,iref); |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| } |
| #endif |
| } |
| } else |
| errno = ENAMETOOLONG; |
| if (!ok) |
| fuse_reply_err(req, errno); |
| else |
| fuse_reply_entry(req, &entry); |
| } |
| |
| static void ntfs_fuse_readlink(fuse_req_t req, fuse_ino_t ino) |
| { |
| ntfs_inode *ni = NULL; |
| ntfs_attr *na = NULL; |
| INTX_FILE *intx_file = NULL; |
| char *buf = (char*)NULL; |
| int res = 0; |
| |
| /* Get inode. */ |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| /* |
| * Reparse point : analyze as a junction point |
| */ |
| if (ni->flags & FILE_ATTR_REPARSE_POINT) { |
| int attr_size; |
| |
| errno = 0; |
| res = 0; |
| buf = ntfs_make_symlink(ni, ctx->abs_mnt_point, &attr_size); |
| if (!buf) { |
| if (errno == EOPNOTSUPP) |
| buf = strdup(ntfs_bad_reparse); |
| if (!buf) |
| res = -errno; |
| } |
| goto exit; |
| } |
| /* Sanity checks. */ |
| if (!(ni->flags & FILE_ATTR_SYSTEM)) { |
| res = -EINVAL; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) { |
| res = -errno; |
| goto exit; |
| } |
| if ((size_t)na->data_size <= sizeof(INTX_FILE_TYPES)) { |
| res = -EINVAL; |
| goto exit; |
| } |
| if ((size_t)na->data_size > sizeof(INTX_FILE_TYPES) + |
| sizeof(ntfschar) * PATH_MAX) { |
| res = -ENAMETOOLONG; |
| goto exit; |
| } |
| /* Receive file content. */ |
| intx_file = (INTX_FILE*)ntfs_malloc(na->data_size); |
| if (!intx_file) { |
| res = -errno; |
| goto exit; |
| } |
| if (ntfs_attr_pread(na, 0, na->data_size, intx_file) != na->data_size) { |
| res = -errno; |
| goto exit; |
| } |
| /* Sanity check. */ |
| if (intx_file->magic != INTX_SYMBOLIC_LINK) { |
| res = -EINVAL; |
| goto exit; |
| } |
| /* Convert link from unicode to local encoding. */ |
| if (ntfs_ucstombs(intx_file->target, (na->data_size - |
| offsetof(INTX_FILE, target)) / sizeof(ntfschar), |
| &buf, 0) < 0) { |
| res = -errno; |
| goto exit; |
| } |
| exit: |
| if (intx_file) |
| free(intx_file); |
| if (na) |
| ntfs_attr_close(na); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_readlink(req, buf); |
| if (buf != ntfs_bad_reparse) |
| free(buf); |
| } |
| |
| 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; |
| int ret = 0; |
| int filenamelen = -1; |
| size_t sz; |
| ntfs_fuse_fill_item_t *current; |
| ntfs_fuse_fill_item_t *newone; |
| |
| if (name_type == FILE_NAME_DOS) |
| return 0; |
| |
| if ((filenamelen = ntfs_ucstombs(name, name_len, &filename, 0)) < 0) { |
| ntfs_log_perror("Filename decoding failed (inode %llu)", |
| (unsigned long long)MREF(mref)); |
| return -1; |
| } |
| /* never return inodes 0 and 1 */ |
| if (MREF(mref) > 1) { |
| struct stat st = { .st_ino = MREF(mref) }; |
| |
| switch (dt_type) { |
| case NTFS_DT_DIR : |
| st.st_mode = S_IFDIR | (0777 & ~ctx->dmask); |
| break; |
| case NTFS_DT_LNK : |
| st.st_mode = S_IFLNK | 0777; |
| break; |
| case NTFS_DT_FIFO : |
| st.st_mode = S_IFIFO; |
| break; |
| case NTFS_DT_SOCK : |
| st.st_mode = S_IFSOCK; |
| break; |
| case NTFS_DT_BLK : |
| st.st_mode = S_IFBLK; |
| break; |
| case NTFS_DT_CHR : |
| st.st_mode = S_IFCHR; |
| break; |
| default : /* unexpected types shown as plain files */ |
| case NTFS_DT_REG : |
| st.st_mode = S_IFREG | (0777 & ~ctx->fmask); |
| break; |
| } |
| |
| #if defined(__APPLE__) || defined(__DARWIN__) |
| /* |
| * Returning file names larger than MAXNAMLEN (255) bytes |
| * causes Darwin/Mac OS X to bug out and skip the entry. |
| */ |
| if (filenamelen > MAXNAMLEN) { |
| ntfs_log_debug("Truncating %d byte filename to " |
| "%d bytes.\n", filenamelen, MAXNAMLEN); |
| ntfs_log_debug(" before: '%s'\n", filename); |
| memset(filename + MAXNAMLEN, 0, filenamelen - MAXNAMLEN); |
| ntfs_log_debug(" after: '%s'\n", filename); |
| } |
| #elif defined(__sun) && defined (__SVR4) |
| /* |
| * Returning file names larger than MAXNAMELEN (256) bytes |
| * causes Solaris/Illumos to return an I/O error from the system |
| * call. |
| * However we also need space for a terminating NULL, or user |
| * space tools will bug out since they expect a NULL terminator. |
| * Effectively the maximum length of a file name is MAXNAMELEN - |
| * 1 (255). |
| */ |
| if (filenamelen > (MAXNAMELEN - 1)) { |
| ntfs_log_debug("Truncating %d byte filename to %d " |
| "bytes.\n", filenamelen, MAXNAMELEN - 1); |
| ntfs_log_debug(" before: '%s'\n", filename); |
| memset(&filename[MAXNAMELEN - 1], 0, |
| filenamelen - (MAXNAMELEN - 1)); |
| ntfs_log_debug(" after: '%s'\n", filename); |
| } |
| #endif /* defined(__APPLE__) || defined(__DARWIN__), ... */ |
| |
| current = fill_ctx->last; |
| sz = fuse_add_direntry(fill_ctx->req, |
| ¤t->buf[current->off], |
| current->bufsize - current->off, |
| filename, &st, current->off + fill_ctx->off); |
| if (!sz || ((current->off + sz) > current->bufsize)) { |
| newone = (ntfs_fuse_fill_item_t*)ntfs_malloc |
| (sizeof(ntfs_fuse_fill_item_t) |
| + current->bufsize); |
| if (newone) { |
| newone->off = 0; |
| newone->bufsize = current->bufsize; |
| newone->next = (ntfs_fuse_fill_item_t*)NULL; |
| current->next = newone; |
| fill_ctx->last = newone; |
| fill_ctx->off += current->off; |
| current = newone; |
| sz = fuse_add_direntry(fill_ctx->req, |
| current->buf, |
| current->bufsize - current->off, |
| filename, &st, fill_ctx->off); |
| if (!sz) { |
| errno = EIO; |
| ntfs_log_error("Could not add a" |
| " directory entry (inode %lld)\n", |
| (unsigned long long)MREF(mref)); |
| } |
| } else { |
| sz = 0; |
| errno = ENOMEM; |
| } |
| } |
| if (sz) { |
| current->off += sz; |
| } else { |
| ret = -1; |
| } |
| } |
| |
| free(filename); |
| return ret; |
| } |
| |
| static void ntfs_fuse_opendir(fuse_req_t req, fuse_ino_t ino, |
| struct fuse_file_info *fi) |
| { |
| int res = 0; |
| ntfs_inode *ni; |
| int accesstype; |
| ntfs_fuse_fill_context_t *fill; |
| struct SECURITY_CONTEXT security; |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (ni) { |
| if (ntfs_fuse_fill_security_context(req, &security)) { |
| if (fi->flags & O_WRONLY) |
| accesstype = S_IWRITE; |
| else |
| if (fi->flags & O_RDWR) |
| accesstype = S_IWRITE | S_IREAD; |
| else |
| accesstype = S_IREAD; |
| if (!ntfs_allowed_access(&security,ni,accesstype)) |
| res = -EACCES; |
| } |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| if (!res) { |
| fill = (ntfs_fuse_fill_context_t*) |
| ntfs_malloc(sizeof(ntfs_fuse_fill_context_t)); |
| if (!fill) |
| res = -errno; |
| else { |
| fill->first = fill->last |
| = (ntfs_fuse_fill_item_t*)NULL; |
| fill->filled = FALSE; |
| fill->ino = ino; |
| fill->off = 0; |
| } |
| fi->fh = (long)fill; |
| } |
| } else |
| res = -errno; |
| if (!res) |
| fuse_reply_open(req, fi); |
| else |
| fuse_reply_err(req, -res); |
| } |
| |
| |
| static void ntfs_fuse_releasedir(fuse_req_t req, |
| fuse_ino_t ino __attribute__((unused)), |
| struct fuse_file_info *fi) |
| { |
| ntfs_fuse_fill_context_t *fill; |
| ntfs_fuse_fill_item_t *current; |
| |
| fill = (ntfs_fuse_fill_context_t*)(long)fi->fh; |
| if (fill && (fill->ino == ino)) { |
| /* make sure to clear results */ |
| current = fill->first; |
| while (current) { |
| current = current->next; |
| free(fill->first); |
| fill->first = current; |
| } |
| fill->ino = 0; |
| free(fill); |
| } |
| fuse_reply_err(req, 0); |
| } |
| |
| static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, |
| off_t off __attribute__((unused)), |
| struct fuse_file_info *fi __attribute__((unused))) |
| { |
| ntfs_fuse_fill_item_t *first; |
| ntfs_fuse_fill_item_t *current; |
| ntfs_fuse_fill_context_t *fill; |
| ntfs_inode *ni; |
| s64 pos = 0; |
| int err = 0; |
| |
| fill = (ntfs_fuse_fill_context_t*)(long)fi->fh; |
| if (fill && (fill->ino == ino)) { |
| if (fill->filled && !off) { |
| /* Rewinding : make sure to clear existing results */ |
| current = fill->first; |
| while (current) { |
| current = current->next; |
| free(fill->first); |
| fill->first = current; |
| } |
| fill->filled = FALSE; |
| } |
| if (!fill->filled) { |
| /* initial call : build the full list */ |
| current = (ntfs_fuse_fill_item_t*)NULL; |
| first = (ntfs_fuse_fill_item_t*)ntfs_malloc |
| (sizeof(ntfs_fuse_fill_item_t) + size); |
| if (first) { |
| first->bufsize = size; |
| first->off = 0; |
| first->next = (ntfs_fuse_fill_item_t*)NULL; |
| fill->req = req; |
| fill->first = first; |
| fill->last = first; |
| fill->off = 0; |
| ni = ntfs_inode_open(ctx->vol,INODE(ino)); |
| if (!ni) |
| err = -errno; |
| else { |
| if (ntfs_readdir(ni, &pos, fill, |
| (ntfs_filldir_t) |
| ntfs_fuse_filler)) |
| err = -errno; |
| fill->filled = TRUE; |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_ATIME); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&err); |
| } |
| if (!err) { |
| off_t loc = 0; |
| /* |
| * In some circumstances, the queue gets |
| * reinitialized by releasedir() + opendir(), |
| * apparently always on end of partial buffer. |
| * Files may be missing or duplicated. |
| */ |
| while (first |
| && ((loc < off) || !first->off)) { |
| loc += first->off; |
| fill->first = first->next; |
| free(first); |
| first = fill->first; |
| } |
| current = first; |
| } |
| } else |
| err = -errno; |
| } else { |
| /* subsequent call : return next non-empty buffer */ |
| current = fill->first; |
| while (current && !current->off) { |
| current = current->next; |
| free(fill->first); |
| fill->first = current; |
| } |
| } |
| if (!err) { |
| if (current) { |
| fuse_reply_buf(req, current->buf, current->off); |
| fill->first = current->next; |
| free(current); |
| } else { |
| fuse_reply_buf(req, (char*)NULL, 0); |
| /* reply sent, now must exit with no error */ |
| } |
| } |
| } else { |
| errno = EIO; |
| err = -errno; |
| ntfs_log_error("Uninitialized fuse_readdir()\n"); |
| } |
| if (err) |
| fuse_reply_err(req, -err); |
| } |
| |
| static void ntfs_fuse_open(fuse_req_t req, fuse_ino_t ino, |
| struct fuse_file_info *fi) |
| { |
| ntfs_inode *ni; |
| ntfs_attr *na; |
| struct open_file *of; |
| int state = 0; |
| char *path = NULL; |
| int res = 0; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| int accesstype; |
| struct SECURITY_CONTEXT security; |
| #endif |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (ni) { |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (na) { |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| if (ntfs_fuse_fill_security_context(req, &security)) { |
| if (fi->flags & O_WRONLY) |
| accesstype = S_IWRITE; |
| else |
| if (fi->flags & O_RDWR) |
| accesstype = S_IWRITE | S_IREAD; |
| else |
| accesstype = S_IREAD; |
| /* check whether requested access is allowed */ |
| if (!ntfs_allowed_access(&security, |
| ni,accesstype)) |
| res = -EACCES; |
| } |
| #endif |
| if ((res >= 0) |
| && (fi->flags & (O_WRONLY | O_RDWR))) { |
| /* mark a future need to compress the last chunk */ |
| if (na->data_flags & ATTR_COMPRESSION_MASK) |
| state |= CLOSE_COMPRESSED; |
| #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
| /* mark a future need to fixup encrypted inode */ |
| if (ctx->efs_raw |
| && !(na->data_flags & ATTR_IS_ENCRYPTED) |
| && (ni->flags & FILE_ATTR_ENCRYPTED)) |
| state |= CLOSE_ENCRYPTED; |
| #endif /* HAVE_SETXATTR */ |
| /* mark a future need to update the mtime */ |
| if (ctx->dmtime) |
| state |= CLOSE_DMTIME; |
| /* deny opening metadata files for writing */ |
| if (ino < FILE_first_user) |
| res = -EPERM; |
| } |
| ntfs_attr_close(na); |
| } else |
| res = -errno; |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } else |
| res = -errno; |
| free(path); |
| if (res >= 0) { |
| of = (struct open_file*)malloc(sizeof(struct open_file)); |
| if (of) { |
| of->parent = 0; |
| of->ino = ino; |
| of->state = state; |
| of->next = ctx->open_files; |
| of->previous = (struct open_file*)NULL; |
| if (ctx->open_files) |
| ctx->open_files->previous = of; |
| ctx->open_files = of; |
| fi->fh = (long)of; |
| } |
| } |
| if (res) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_open(req, fi); |
| } |
| |
| static void ntfs_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, |
| off_t offset, |
| struct fuse_file_info *fi __attribute__((unused))) |
| { |
| ntfs_inode *ni = NULL; |
| ntfs_attr *na = NULL; |
| int res; |
| char *buf = (char*)NULL; |
| s64 total = 0; |
| s64 max_read; |
| |
| if (!size) { |
| res = 0; |
| goto exit; |
| } |
| buf = (char*)ntfs_malloc(size); |
| if (!buf) { |
| res = -errno; |
| goto exit; |
| } |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) { |
| res = -errno; |
| goto exit; |
| } |
| max_read = na->data_size; |
| #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
| /* limit reads at next 512 byte boundary for encrypted attributes */ |
| if (ctx->efs_raw |
| && max_read |
| && (na->data_flags & ATTR_IS_ENCRYPTED) |
| && NAttrNonResident(na)) { |
| max_read = ((na->data_size+511) & ~511) + 2; |
| } |
| #endif /* HAVE_SETXATTR */ |
| if (offset + (off_t)size > max_read) { |
| if (max_read < offset) |
| goto ok; |
| size = max_read - offset; |
| } |
| while (size > 0) { |
| s64 ret = ntfs_attr_pread(na, offset, size, buf + total); |
| if (ret != (s64)size) |
| ntfs_log_perror("ntfs_attr_pread error reading inode %lld at " |
| "offset %lld: %lld <> %lld", (long long)ni->mft_no, |
| (long long)offset, (long long)size, (long long)ret); |
| if (ret <= 0 || ret > (s64)size) { |
| res = (ret < 0) ? -errno : -EIO; |
| goto exit; |
| } |
| size -= ret; |
| offset += ret; |
| total += ret; |
| } |
| ok: |
| ntfs_fuse_update_times(na->ni, NTFS_UPDATE_ATIME); |
| res = total; |
| exit: |
| if (na) |
| ntfs_attr_close(na); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_buf(req, buf, res); |
| free(buf); |
| } |
| |
| static void ntfs_fuse_write(fuse_req_t req, fuse_ino_t ino, const char *buf, |
| size_t size, off_t offset, |
| struct fuse_file_info *fi __attribute__((unused))) |
| { |
| ntfs_inode *ni = NULL; |
| ntfs_attr *na = NULL; |
| int res, total = 0; |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) { |
| res = -errno; |
| goto exit; |
| } |
| while (size) { |
| s64 ret = ntfs_attr_pwrite(na, offset, size, buf + total); |
| if (ret <= 0) { |
| res = -errno; |
| goto exit; |
| } |
| size -= ret; |
| offset += ret; |
| total += ret; |
| } |
| res = total; |
| if ((res > 0) |
| && (!ctx->dmtime |
| || (sle64_to_cpu(ntfs_current_time()) |
| - sle64_to_cpu(ni->last_data_change_time)) > ctx->dmtime)) |
| ntfs_fuse_update_times(na->ni, NTFS_UPDATE_MCTIME); |
| exit: |
| if (na) |
| ntfs_attr_close(na); |
| if (total) |
| set_archive(ni); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_write(req, res); |
| } |
| |
| static int ntfs_fuse_chmod(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, |
| mode_t mode, struct stat *stbuf) |
| { |
| int res = 0; |
| ntfs_inode *ni; |
| |
| /* Unsupported if inherit or no user mapping has been defined */ |
| if ((!scx->mapping[MAPUSERS] || ctx->inherit) |
| && !ctx->silent) { |
| res = -EOPNOTSUPP; |
| } else { |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| res = -errno; |
| else { |
| /* ignore if Windows inheritance is forced */ |
| if (scx->mapping[MAPUSERS] && !ctx->inherit) { |
| if (ntfs_set_mode(scx, ni, mode)) |
| res = -errno; |
| else { |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| /* |
| * Must return updated times, and |
| * inode has been updated, so hope |
| * we get no further errors |
| */ |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| } |
| NInoSetDirty(ni); |
| } else |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } |
| return res; |
| } |
| |
| static int ntfs_fuse_chown(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, |
| uid_t uid, gid_t gid, struct stat *stbuf) |
| { |
| ntfs_inode *ni; |
| int res; |
| |
| /* Unsupported if inherit or no user mapping has been defined */ |
| if ((!scx->mapping[MAPUSERS] || ctx->inherit) |
| && !ctx->silent |
| && ((uid != ctx->uid) || (gid != ctx->gid))) |
| res = -EOPNOTSUPP; |
| else { |
| res = 0; |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| res = -errno; |
| else { |
| /* ignore if Windows inheritance is forced */ |
| if (scx->mapping[MAPUSERS] |
| && !ctx->inherit |
| && (((int)uid != -1) || ((int)gid != -1))) { |
| if (ntfs_set_owner(scx, ni, uid, gid)) |
| res = -errno; |
| else { |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| /* |
| * Must return updated times, and |
| * inode has been updated, so hope |
| * we get no further errors |
| */ |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| } |
| } else |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } |
| return (res); |
| } |
| |
| static int ntfs_fuse_chownmod(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, |
| uid_t uid, gid_t gid, mode_t mode, struct stat *stbuf) |
| { |
| ntfs_inode *ni; |
| int res; |
| |
| /* Unsupported if inherit or no user mapping has been defined */ |
| if ((!scx->mapping[MAPUSERS] || ctx->inherit) |
| && !ctx->silent |
| && ((uid != ctx->uid) || (gid != ctx->gid))) |
| res = -EOPNOTSUPP; |
| else { |
| res = 0; |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| res = -errno; |
| else { |
| /* ignore if Windows inheritance is forced */ |
| if (scx->mapping[MAPUSERS] && !ctx->inherit) { |
| if (ntfs_set_ownmod(scx, ni, uid, gid, mode)) |
| res = -errno; |
| else { |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| /* |
| * Must return updated times, and |
| * inode has been updated, so hope |
| * we get no further errors |
| */ |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| } |
| } else |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } |
| return (res); |
| } |
| |
| static int ntfs_fuse_trunc(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| off_t size, BOOL chkwrite, struct stat *stbuf) |
| #else |
| off_t size, BOOL chkwrite __attribute__((unused)), |
| struct stat *stbuf) |
| #endif |
| { |
| ntfs_inode *ni = NULL; |
| ntfs_attr *na = NULL; |
| int res; |
| s64 oldsize; |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| goto exit; |
| |
| /* deny truncating metadata files */ |
| if (ino < FILE_first_user) { |
| errno = EPERM; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) |
| goto exit; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| /* |
| * deny truncation if cannot write to file |
| * (already checked for ftruncate()) |
| */ |
| if (scx->mapping[MAPUSERS] |
| && chkwrite |
| && !ntfs_allowed_access(scx, ni, S_IWRITE)) { |
| errno = EACCES; |
| goto exit; |
| } |
| #endif |
| /* |
| * for compressed files, upsizing is done by inserting a final |
| * zero, which is optimized as creating a hole when possible. |
| */ |
| oldsize = na->data_size; |
| if ((na->data_flags & ATTR_COMPRESSION_MASK) |
| && (size > na->initialized_size)) { |
| char zero = 0; |
| if (ntfs_attr_pwrite(na, size - 1, 1, &zero) <= 0) |
| goto exit; |
| } else |
| if (ntfs_attr_truncate(na, size)) |
| goto exit; |
| if (oldsize != size) |
| set_archive(ni); |
| |
| ntfs_fuse_update_times(na->ni, NTFS_UPDATE_MCTIME); |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| errno = (res ? -res : 0); |
| exit: |
| res = -errno; |
| ntfs_attr_close(na); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| return res; |
| } |
| |
| #if defined(HAVE_UTIMENSAT) & defined(FUSE_SET_ATTR_ATIME_NOW) |
| |
| static int ntfs_fuse_utimens(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, |
| struct stat *stin, struct stat *stbuf, int to_set) |
| { |
| ntfs_inode *ni; |
| int res = 0; |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| return -errno; |
| |
| /* no check or update if both UTIME_OMIT */ |
| if (to_set & (FUSE_SET_ATTR_ATIME + FUSE_SET_ATTR_MTIME)) { |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| if (ntfs_allowed_as_owner(scx, ni) |
| || ((to_set & FUSE_SET_ATTR_ATIME_NOW) |
| && (to_set & FUSE_SET_ATTR_MTIME_NOW) |
| && ntfs_allowed_access(scx, ni, S_IWRITE))) { |
| #endif |
| ntfs_time_update_flags mask = NTFS_UPDATE_CTIME; |
| |
| if (to_set & FUSE_SET_ATTR_ATIME_NOW) |
| mask |= NTFS_UPDATE_ATIME; |
| else |
| if (to_set & FUSE_SET_ATTR_ATIME) |
| ni->last_access_time |
| = timespec2ntfs(stin->st_atim); |
| if (to_set & FUSE_SET_ATTR_MTIME_NOW) |
| mask |= NTFS_UPDATE_MTIME; |
| else |
| if (to_set & FUSE_SET_ATTR_MTIME) |
| ni->last_data_change_time |
| = timespec2ntfs(stin->st_mtim); |
| ntfs_inode_update_times(ni, mask); |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| } else |
| res = -errno; |
| #endif |
| } |
| if (!res) |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| return res; |
| } |
| |
| #else /* defined(HAVE_UTIMENSAT) & defined(FUSE_SET_ATTR_ATIME_NOW) */ |
| |
| static int ntfs_fuse_utime(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, |
| struct stat *stin, struct stat *stbuf) |
| { |
| ntfs_inode *ni; |
| int res = 0; |
| struct timespec actime; |
| struct timespec modtime; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| BOOL ownerok; |
| BOOL writeok; |
| #endif |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| return -errno; |
| |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ownerok = ntfs_allowed_as_owner(scx, ni); |
| if (stin) { |
| /* |
| * fuse never calls with a NULL buf and we do not |
| * know whether the specific condition can be applied |
| * So we have to accept updating by a non-owner having |
| * write access. |
| */ |
| writeok = !ownerok |
| && (stin->st_atime == stin->st_mtime) |
| && ntfs_allowed_access(scx, ni, S_IWRITE); |
| /* Must be owner */ |
| if (!ownerok && !writeok) |
| res = (stin->st_atime == stin->st_mtime |
| ? -EACCES : -EPERM); |
| else { |
| actime.tv_sec = stin->st_atime; |
| actime.tv_nsec = 0; |
| modtime.tv_sec = stin->st_mtime; |
| modtime.tv_nsec = 0; |
| ni->last_access_time = timespec2ntfs(actime); |
| ni->last_data_change_time = timespec2ntfs(modtime); |
| ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); |
| } |
| } else { |
| /* Must be owner or have write access */ |
| writeok = !ownerok |
| && ntfs_allowed_access(scx, ni, S_IWRITE); |
| if (!ownerok && !writeok) |
| res = -EACCES; |
| else |
| ntfs_inode_update_times(ni, NTFS_UPDATE_AMCTIME); |
| } |
| #else |
| if (stin) { |
| actime.tv_sec = stin->st_atime; |
| actime.tv_nsec = 0; |
| modtime.tv_sec = stin->st_mtime; |
| modtime.tv_nsec = 0; |
| ni->last_access_time = timespec2ntfs(actime); |
| ni->last_data_change_time = timespec2ntfs(modtime); |
| ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); |
| } else |
| ntfs_inode_update_times(ni, NTFS_UPDATE_AMCTIME); |
| #endif |
| |
| res = ntfs_fuse_getstat(scx, ni, stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| return res; |
| } |
| |
| #endif /* defined(HAVE_UTIMENSAT) & defined(FUSE_SET_ATTR_ATIME_NOW) */ |
| |
| static void ntfs_fuse_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, |
| int to_set, struct fuse_file_info *fi __attribute__((unused))) |
| { |
| struct stat stbuf; |
| ntfs_inode *ni; |
| int res; |
| struct SECURITY_CONTEXT security; |
| |
| res = 0; |
| ntfs_fuse_fill_security_context(req, &security); |
| /* no flags */ |
| if (!(to_set |
| & (FUSE_SET_ATTR_MODE |
| | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |
| | FUSE_SET_ATTR_SIZE |
| | FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) { |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) |
| res = -errno; |
| else { |
| res = ntfs_fuse_getstat(&security, ni, &stbuf); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } |
| /* some set of uid/gid/mode */ |
| if (to_set |
| & (FUSE_SET_ATTR_MODE |
| | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { |
| switch (to_set |
| & (FUSE_SET_ATTR_MODE |
| | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { |
| case FUSE_SET_ATTR_MODE : |
| res = ntfs_fuse_chmod(&security, ino, |
| attr->st_mode & 07777, &stbuf); |
| break; |
| case FUSE_SET_ATTR_UID : |
| res = ntfs_fuse_chown(&security, ino, attr->st_uid, |
| (gid_t)-1, &stbuf); |
| break; |
| case FUSE_SET_ATTR_GID : |
| res = ntfs_fuse_chown(&security, ino, (uid_t)-1, |
| attr->st_gid, &stbuf); |
| break; |
| case FUSE_SET_ATTR_UID + FUSE_SET_ATTR_GID : |
| res = ntfs_fuse_chown(&security, ino, attr->st_uid, |
| attr->st_gid, &stbuf); |
| break; |
| case FUSE_SET_ATTR_UID + FUSE_SET_ATTR_MODE: |
| res = ntfs_fuse_chownmod(&security, ino, attr->st_uid, |
| (gid_t)-1,attr->st_mode, |
| &stbuf); |
| break; |
| case FUSE_SET_ATTR_GID + FUSE_SET_ATTR_MODE: |
| res = ntfs_fuse_chownmod(&security, ino, (uid_t)-1, |
| attr->st_gid,attr->st_mode, |
| &stbuf); |
| break; |
| case FUSE_SET_ATTR_UID + FUSE_SET_ATTR_GID + FUSE_SET_ATTR_MODE: |
| res = ntfs_fuse_chownmod(&security, ino, attr->st_uid, |
| attr->st_gid,attr->st_mode, &stbuf); |
| break; |
| default : |
| break; |
| } |
| } |
| /* size */ |
| if (!res && (to_set & FUSE_SET_ATTR_SIZE)) { |
| res = ntfs_fuse_trunc(&security, ino, attr->st_size, |
| !fi, &stbuf); |
| } |
| /* some set of atime/mtime */ |
| if (!res && (to_set & (FUSE_SET_ATTR_ATIME + FUSE_SET_ATTR_MTIME))) { |
| #if defined(HAVE_UTIMENSAT) & defined(FUSE_SET_ATTR_ATIME_NOW) |
| res = ntfs_fuse_utimens(&security, ino, attr, &stbuf, to_set); |
| #else /* defined(HAVE_UTIMENSAT) & defined(FUSE_SET_ATTR_ATIME_NOW) */ |
| res = ntfs_fuse_utime(&security, ino, attr, &stbuf); |
| #endif /* defined(HAVE_UTIMENSAT) & defined(FUSE_SET_ATTR_ATIME_NOW) */ |
| } |
| if (res) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_attr(req, &stbuf, ATTR_TIMEOUT); |
| } |
| |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| |
| static void ntfs_fuse_access(fuse_req_t req, fuse_ino_t ino, int mask) |
| { |
| int res = 0; |
| int mode; |
| ntfs_inode *ni; |
| struct SECURITY_CONTEXT security; |
| |
| /* JPA return unsupported if no user mapping has been defined */ |
| if (!ntfs_fuse_fill_security_context(req, &security)) { |
| if (ctx->silent) |
| res = 0; |
| else |
| res = -EOPNOTSUPP; |
| } else { |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| } else { |
| mode = 0; |
| if (mask & (X_OK | W_OK | R_OK)) { |
| if (mask & X_OK) mode += S_IEXEC; |
| if (mask & W_OK) mode += S_IWRITE; |
| if (mask & R_OK) mode += S_IREAD; |
| if (!ntfs_allowed_access(&security, |
| ni, mode)) |
| res = -errno; |
| } |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| #endif /* !KERNELPERMS | (POSIXACLS & !KERNELACLS) */ |
| |
| static int ntfs_fuse_create(fuse_req_t req, fuse_ino_t parent, const char *name, |
| mode_t typemode, dev_t dev, |
| struct fuse_entry_param *e, |
| const char *target, struct fuse_file_info *fi) |
| { |
| ntfschar *uname = NULL, *utarget = NULL; |
| ntfs_inode *dir_ni = NULL, *ni; |
| struct open_file *of; |
| int state = 0; |
| le32 securid; |
| gid_t gid; |
| mode_t dsetgid; |
| mode_t type = typemode & ~07777; |
| mode_t perm; |
| struct SECURITY_CONTEXT security; |
| int res = 0, uname_len, utarget_len; |
| |
| /* Generate unicode filename. */ |
| uname_len = ntfs_mbstoucs(name, &uname); |
| if ((uname_len < 0) |
| || (ctx->windows_names |
| && ntfs_forbidden_names(ctx->vol,uname,uname_len))) { |
| res = -errno; |
| goto exit; |
| } |
| /* Open parent directory. */ |
| dir_ni = ntfs_inode_open(ctx->vol, INODE(parent)); |
| if (!dir_ni) { |
| res = -errno; |
| goto exit; |
| } |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| /* make sure parent directory is writeable and executable */ |
| if (!ntfs_fuse_fill_security_context(req, &security) |
| || ntfs_allowed_create(&security, |
| dir_ni, &gid, &dsetgid)) { |
| #else |
| ntfs_fuse_fill_security_context(req, &security); |
| ntfs_allowed_create(&security, dir_ni, &gid, &dsetgid); |
| #endif |
| if (S_ISDIR(type)) |
| perm = (typemode & ~ctx->dmask & 0777) |
| | (dsetgid & S_ISGID); |
| else |
| perm = typemode & ~ctx->fmask & 0777; |
| /* |
| * Try to get a security id available for |
| * file creation (from inheritance or argument). |
| * This is not possible for NTFS 1.x, and we will |
| * have to build a security attribute later. |
| */ |
| if (!ctx->security.mapping[MAPUSERS]) |
| securid = const_cpu_to_le32(0); |
| else |
| if (ctx->inherit) |
| securid = ntfs_inherited_id(&security, |
| dir_ni, S_ISDIR(type)); |
| else |
| #if POSIXACLS |
| securid = ntfs_alloc_securid(&security, |
| security.uid, gid, |
| dir_ni, perm, S_ISDIR(type)); |
| #else |
| securid = ntfs_alloc_securid(&security, |
| security.uid, gid, |
| perm & ~security.umask, S_ISDIR(type)); |
| #endif |
| /* Create object specified in @type. */ |
| switch (type) { |
| case S_IFCHR: |
| case S_IFBLK: |
| ni = ntfs_create_device(dir_ni, securid, |
| uname, uname_len, type, dev); |
| break; |
| case S_IFLNK: |
| utarget_len = ntfs_mbstoucs(target, &utarget); |
| if (utarget_len < 0) { |
| res = -errno; |
| goto exit; |
| } |
| ni = ntfs_create_symlink(dir_ni, securid, |
| uname, uname_len, |
| utarget, utarget_len); |
| break; |
| default: |
| ni = ntfs_create(dir_ni, securid, uname, |
| uname_len, type); |
| break; |
| } |
| if (ni) { |
| /* |
| * set the security attribute if a security id |
| * could not be allocated (eg NTFS 1.x) |
| */ |
| if (ctx->security.mapping[MAPUSERS]) { |
| #if POSIXACLS |
| if (!securid |
| && ntfs_set_inherited_posix(&security, ni, |
| security.uid, gid, |
| dir_ni, perm) < 0) |
| set_fuse_error(&res); |
| #else |
| if (!securid |
| && ntfs_set_owner_mode(&security, ni, |
| security.uid, gid, |
| perm & ~security.umask) < 0) |
| set_fuse_error(&res); |
| #endif |
| } |
| set_archive(ni); |
| /* mark a need to compress the end of file */ |
| if (fi && (ni->flags & FILE_ATTR_COMPRESSED)) { |
| state |= CLOSE_COMPRESSED; |
| } |
| #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
| /* mark a future need to fixup encrypted inode */ |
| if (fi |
| && ctx->efs_raw |
| && (ni->flags & FILE_ATTR_ENCRYPTED)) |
| state |= CLOSE_ENCRYPTED; |
| #endif /* HAVE_SETXATTR */ |
| if (fi && ctx->dmtime) |
| state |= CLOSE_DMTIME; |
| ntfs_inode_update_mbsname(dir_ni, name, ni->mft_no); |
| NInoSetDirty(ni); |
| e->ino = ni->mft_no; |
| e->generation = 1; |
| e->attr_timeout = ATTR_TIMEOUT; |
| e->entry_timeout = ENTRY_TIMEOUT; |
| res = ntfs_fuse_getstat(&security, ni, &e->attr); |
| /* |
| * closing ni requires access to dir_ni to |
| * synchronize the index, avoid double opening. |
| */ |
| if (ntfs_inode_close_in_dir(ni, dir_ni)) |
| set_fuse_error(&res); |
| ntfs_fuse_update_times(dir_ni, NTFS_UPDATE_MCTIME); |
| } else |
| res = -errno; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| } else |
| res = -errno; |
| #endif |
| |
| exit: |
| free(uname); |
| if (ntfs_inode_close(dir_ni)) |
| set_fuse_error(&res); |
| if (utarget) |
| free(utarget); |
| if ((res >= 0) && fi) { |
| of = (struct open_file*)malloc(sizeof(struct open_file)); |
| if (of) { |
| of->parent = 0; |
| of->ino = e->ino; |
| of->state = state; |
| of->next = ctx->open_files; |
| of->previous = (struct open_file*)NULL; |
| if (ctx->open_files) |
| ctx->open_files->previous = of; |
| ctx->open_files = of; |
| fi->fh = (long)of; |
| } |
| } |
| return res; |
| } |
| |
| static void ntfs_fuse_create_file(fuse_req_t req, fuse_ino_t parent, |
| const char *name, mode_t mode, |
| struct fuse_file_info *fi) |
| { |
| int res; |
| struct fuse_entry_param entry; |
| |
| res = ntfs_fuse_create(req, parent, name, mode & (S_IFMT | 07777), |
| 0, &entry, NULL, fi); |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_create(req, &entry, fi); |
| } |
| |
| static void ntfs_fuse_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, |
| mode_t mode, dev_t rdev) |
| { |
| int res; |
| struct fuse_entry_param e; |
| |
| res = ntfs_fuse_create(req, parent, name, mode & (S_IFMT | 07777), |
| rdev, &e,NULL,(struct fuse_file_info*)NULL); |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_entry(req, &e); |
| } |
| |
| static void ntfs_fuse_symlink(fuse_req_t req, const char *target, |
| fuse_ino_t parent, const char *name) |
| { |
| int res; |
| struct fuse_entry_param entry; |
| |
| res = ntfs_fuse_create(req, parent, name, S_IFLNK, 0, |
| &entry, target, (struct fuse_file_info*)NULL); |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_entry(req, &entry); |
| } |
| |
| |
| static int ntfs_fuse_newlink(fuse_req_t req __attribute__((unused)), |
| fuse_ino_t ino, fuse_ino_t newparent, |
| const char *newname, struct fuse_entry_param *e) |
| { |
| ntfschar *uname = NULL; |
| ntfs_inode *dir_ni = NULL, *ni; |
| int res = 0, uname_len; |
| struct SECURITY_CONTEXT security; |
| |
| /* Open file for which create hard link. */ |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| |
| /* Do not accept linking to a directory (except for renaming) */ |
| if (e && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { |
| errno = EPERM; |
| res = -errno; |
| goto exit; |
| } |
| /* Generate unicode filename. */ |
| uname_len = ntfs_mbstoucs(newname, &uname); |
| if ((uname_len < 0) |
| || (ctx->windows_names |
| && ntfs_forbidden_names(ctx->vol,uname,uname_len))) { |
| res = -errno; |
| goto exit; |
| } |
| /* Open parent directory. */ |
| dir_ni = ntfs_inode_open(ctx->vol, INODE(newparent)); |
| if (!dir_ni) { |
| res = -errno; |
| goto exit; |
| } |
| |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| /* make sure the target parent directory is writeable */ |
| if (ntfs_fuse_fill_security_context(req, &security) |
| && !ntfs_allowed_access(&security,dir_ni,S_IWRITE + S_IEXEC)) |
| res = -EACCES; |
| else |
| #else |
| ntfs_fuse_fill_security_context(req, &security); |
| #endif |
| { |
| if (ntfs_link(ni, dir_ni, uname, uname_len)) { |
| res = -errno; |
| goto exit; |
| } |
| ntfs_inode_update_mbsname(dir_ni, newname, ni->mft_no); |
| if (e) { |
| e->ino = ni->mft_no; |
| e->generation = 1; |
| e->attr_timeout = ATTR_TIMEOUT; |
| e->entry_timeout = ENTRY_TIMEOUT; |
| res = ntfs_fuse_getstat(&security, ni, &e->attr); |
| } |
| set_archive(ni); |
| ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); |
| ntfs_fuse_update_times(dir_ni, NTFS_UPDATE_MCTIME); |
| } |
| exit: |
| /* |
| * Must close dir_ni first otherwise ntfs_inode_sync_file_name(ni) |
| * may fail because ni may not be in parent's index on the disk yet. |
| */ |
| if (ntfs_inode_close(dir_ni)) |
| set_fuse_error(&res); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| free(uname); |
| return (res); |
| } |
| |
| static void ntfs_fuse_link(fuse_req_t req, fuse_ino_t ino, |
| fuse_ino_t newparent, const char *newname) |
| { |
| struct fuse_entry_param entry; |
| int res; |
| |
| res = ntfs_fuse_newlink(req, ino, newparent, newname, &entry); |
| if (res) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_entry(req, &entry); |
| } |
| |
| static int ntfs_fuse_rm(fuse_req_t req, fuse_ino_t parent, const char *name, |
| enum RM_TYPES rm_type __attribute__((unused))) |
| { |
| ntfschar *uname = NULL; |
| ntfschar *ugname; |
| ntfs_inode *dir_ni = NULL, *ni = NULL; |
| int res = 0, uname_len; |
| int ugname_len; |
| u64 iref; |
| fuse_ino_t ino; |
| struct open_file *of; |
| char ghostname[GHOSTLTH]; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| struct SECURITY_CONTEXT security; |
| #endif |
| |
| /* Open parent directory. */ |
| dir_ni = ntfs_inode_open(ctx->vol, INODE(parent)); |
| if (!dir_ni) { |
| res = -errno; |
| goto exit; |
| } |
| /* Generate unicode filename. */ |
| uname_len = ntfs_mbstoucs(name, &uname); |
| if (uname_len < 0) { |
| res = -errno; |
| goto exit; |
| } |
| /* Open object for delete. */ |
| iref = ntfs_inode_lookup_by_mbsname(dir_ni, name); |
| if (iref == (u64)-1) { |
| res = -errno; |
| goto exit; |
| } |
| ino = (fuse_ino_t)MREF(iref); |
| /* deny unlinking metadata files */ |
| if (ino < FILE_first_user) { |
| res = -EPERM; |
| goto exit; |
| } |
| |
| ni = ntfs_inode_open(ctx->vol, ino); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| |
| #if defined(__sun) && defined (__SVR4) |
| /* on Solaris : deny unlinking directories */ |
| if (rm_type |
| == (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ? RM_LINK : RM_DIR)) { |
| errno = EPERM; |
| res = -errno; |
| goto exit; |
| } |
| #endif /* defined(__sun) && defined (__SVR4) */ |
| |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| /* JPA deny unlinking if directory is not writable and executable */ |
| if (ntfs_fuse_fill_security_context(req, &security) |
| && !ntfs_allowed_dir_access(&security, dir_ni, ino, ni, |
| S_IEXEC + S_IWRITE + S_ISVTX)) { |
| errno = EACCES; |
| res = -errno; |
| goto exit; |
| } |
| #endif |
| /* |
| * We keep one open_file record per opening, to avoid |
| * having to check the list of open files when opening |
| * and closing (which are more frequent than unlinking). |
| * As a consequence, we may have to create several |
| * ghosts names for the same file. |
| * The file may have been opened with a different name |
| * in a different parent directory. The ghost is |
| * nevertheless created in the parent directory of the |
| * name being unlinked, and permissions to do so are the |
| * same as required for unlinking. |
| */ |
| for (of=ctx->open_files; of; of = of->next) { |
| if ((of->ino == ino) && !(of->state & CLOSE_GHOST)) { |
| /* file was open, create a ghost in unlink parent */ |
| ntfs_inode *gni; |
| u64 gref; |
| |
| /* ni has to be closed for linking ghost */ |
| if (ni) { |
| if (ntfs_inode_close(ni)) { |
| res = -errno; |
| goto exit; |
| } |
| ni = (ntfs_inode*)NULL; |
| } |
| of->state |= CLOSE_GHOST; |
| of->parent = parent; |
| of->ghost = ++ctx->latest_ghost; |
| sprintf(ghostname,ghostformat,of->ghost); |
| /* Generate unicode filename. */ |
| ugname = (ntfschar*)NULL; |
| ugname_len = ntfs_mbstoucs(ghostname, &ugname); |
| if (ugname_len < 0) { |
| res = -errno; |
| goto exit; |
| } |
| /* sweep existing ghost if any, ignoring errors */ |
| gref = ntfs_inode_lookup_by_mbsname(dir_ni, ghostname); |
| if (gref != (u64)-1) { |
| gni = ntfs_inode_open(ctx->vol, MREF(gref)); |
| ntfs_delete(ctx->vol, (char*)NULL, gni, dir_ni, |
| ugname, ugname_len); |
| /* ntfs_delete() always closes gni and dir_ni */ |
| dir_ni = (ntfs_inode*)NULL; |
| } else { |
| if (ntfs_inode_close(dir_ni)) { |
| res = -errno; |
| goto out; |
| } |
| dir_ni = (ntfs_inode*)NULL; |
| } |
| free(ugname); |
| res = ntfs_fuse_newlink(req, of->ino, parent, ghostname, |
| (struct fuse_entry_param*)NULL); |
| if (res) |
| goto out; |
| /* now reopen then parent directory */ |
| dir_ni = ntfs_inode_open(ctx->vol, INODE(parent)); |
| if (!dir_ni) { |
| res = -errno; |
| goto exit; |
| } |
| } |
| } |
| if (!ni) { |
| ni = ntfs_inode_open(ctx->vol, ino); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| } |
| if (ntfs_delete(ctx->vol, (char*)NULL, ni, dir_ni, |
| uname, uname_len)) |
| res = -errno; |
| /* ntfs_delete() always closes ni and dir_ni */ |
| ni = dir_ni = NULL; |
| exit: |
| if (ntfs_inode_close(ni) && !res) |
| res = -errno; |
| if (ntfs_inode_close(dir_ni) && !res) |
| res = -errno; |
| out : |
| free(uname); |
| return res; |
| } |
| |
| static void ntfs_fuse_unlink(fuse_req_t req, fuse_ino_t parent, |
| const char *name) |
| { |
| int res; |
| |
| res = ntfs_fuse_rm(req, parent, name, RM_LINK); |
| if (res) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| static int ntfs_fuse_safe_rename(fuse_req_t req, fuse_ino_t ino, |
| fuse_ino_t parent, const char *name, fuse_ino_t xino, |
| fuse_ino_t newparent, const char *newname, |
| const char *tmp) |
| { |
| int ret; |
| |
| ntfs_log_trace("Entering\n"); |
| |
| ret = ntfs_fuse_newlink(req, xino, newparent, tmp, |
| (struct fuse_entry_param*)NULL); |
| if (ret) |
| return ret; |
| |
| ret = ntfs_fuse_rm(req, newparent, newname, RM_ANY); |
| if (!ret) { |
| |
| ret = ntfs_fuse_newlink(req, ino, newparent, newname, |
| (struct fuse_entry_param*)NULL); |
| if (ret) |
| goto restore; |
| |
| ret = ntfs_fuse_rm(req, parent, name, RM_ANY); |
| if (ret) { |
| if (ntfs_fuse_rm(req, newparent, newname, RM_ANY)) |
| goto err; |
| goto restore; |
| } |
| } |
| |
| goto cleanup; |
| restore: |
| if (ntfs_fuse_newlink(req, xino, newparent, newname, |
| (struct fuse_entry_param*)NULL)) { |
| err: |
| ntfs_log_perror("Rename failed. Existing file '%s' was renamed " |
| "to '%s'", newname, tmp); |
| } else { |
| cleanup: |
| /* |
| * Condition for this unlink has already been checked in |
| * "ntfs_fuse_rename_existing_dest()", so it should never |
| * fail (unless concurrent access to directories when fuse |
| * is multithreaded) |
| */ |
| if (ntfs_fuse_rm(req, newparent, tmp, RM_ANY) < 0) |
| ntfs_log_perror("Rename failed. Existing file '%s' still present " |
| "as '%s'", newname, tmp); |
| } |
| return ret; |
| } |
| |
| static int ntfs_fuse_rename_existing_dest(fuse_req_t req, fuse_ino_t ino, |
| fuse_ino_t parent, const char *name, |
| fuse_ino_t xino, fuse_ino_t newparent, |
| const char *newname) |
| { |
| int ret, len; |
| char *tmp; |
| const char *ext = ".ntfs-3g-"; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ntfs_inode *newdir_ni; |
| struct SECURITY_CONTEXT security; |
| #endif |
| |
| ntfs_log_trace("Entering\n"); |
| |
| len = strlen(newname) + strlen(ext) + 10 + 1; /* wc(str(2^32)) + \0 */ |
| tmp = (char*)ntfs_malloc(len); |
| if (!tmp) |
| return -errno; |
| |
| ret = snprintf(tmp, len, "%s%s%010d", newname, ext, ++ntfs_sequence); |
| if (ret != len - 1) { |
| ntfs_log_error("snprintf failed: %d != %d\n", ret, len - 1); |
| ret = -EOVERFLOW; |
| } else { |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| /* |
| * Make sure existing dest can be removed. |
| * This is only needed if parent directory is |
| * sticky, because in this situation condition |
| * for unlinking is different from condition for |
| * linking |
| */ |
| newdir_ni = ntfs_inode_open(ctx->vol, INODE(newparent)); |
| if (newdir_ni) { |
| if (!ntfs_fuse_fill_security_context(req,&security) |
| || ntfs_allowed_dir_access(&security, newdir_ni, |
| xino, (ntfs_inode*)NULL, |
| S_IEXEC + S_IWRITE + S_ISVTX)) { |
| if (ntfs_inode_close(newdir_ni)) |
| ret = -errno; |
| else |
| ret = ntfs_fuse_safe_rename(req, ino, |
| parent, name, xino, |
| newparent, newname, |
| tmp); |
| } else { |
| ntfs_inode_close(newdir_ni); |
| ret = -EACCES; |
| } |
| } else |
| ret = -errno; |
| #else |
| ret = ntfs_fuse_safe_rename(req, ino, parent, name, |
| xino, newparent, newname, tmp); |
| #endif |
| } |
| free(tmp); |
| return ret; |
| } |
| |
| static void ntfs_fuse_rename(fuse_req_t req, fuse_ino_t parent, |
| const char *name, fuse_ino_t newparent, |
| const char *newname) |
| { |
| int ret; |
| fuse_ino_t ino; |
| fuse_ino_t xino; |
| ntfs_inode *ni; |
| |
| ntfs_log_debug("rename: old: '%s' new: '%s'\n", name, newname); |
| |
| /* |
| * FIXME: Rename should be atomic. |
| */ |
| |
| ino = ntfs_fuse_inode_lookup(parent, name); |
| if (ino == (fuse_ino_t)-1) { |
| ret = -errno; |
| goto out; |
| } |
| /* Check whether target is present */ |
| xino = ntfs_fuse_inode_lookup(newparent, newname); |
| if (xino != (fuse_ino_t)-1) { |
| /* |
| * Target exists : no need to check whether it |
| * designates the same inode, this has already |
| * been checked (by fuse ?) |
| */ |
| ni = ntfs_inode_open(ctx->vol, INODE(xino)); |
| if (!ni) |
| ret = -errno; |
| else { |
| ret = ntfs_check_empty_dir(ni); |
| if (ret < 0) { |
| ret = -errno; |
| ntfs_inode_close(ni); |
| goto out; |
| } |
| |
| if (ntfs_inode_close(ni)) { |
| set_fuse_error(&ret); |
| goto out; |
| } |
| ret = ntfs_fuse_rename_existing_dest(req, ino, parent, |
| name, xino, newparent, newname); |
| } |
| } else { |
| /* target does not exist */ |
| ret = ntfs_fuse_newlink(req, ino, newparent, newname, |
| (struct fuse_entry_param*)NULL); |
| if (ret) |
| goto out; |
| |
| ret = ntfs_fuse_rm(req, parent, name, RM_ANY); |
| if (ret) |
| ntfs_fuse_rm(req, newparent, newname, RM_ANY); |
| } |
| out: |
| if (ret) |
| fuse_reply_err(req, -ret); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| static void ntfs_fuse_release(fuse_req_t req, fuse_ino_t ino, |
| struct fuse_file_info *fi) |
| { |
| ntfs_inode *ni = NULL; |
| ntfs_attr *na = NULL; |
| struct open_file *of; |
| char ghostname[GHOSTLTH]; |
| int res; |
| |
| of = (struct open_file*)(long)fi->fh; |
| /* Only for marked descriptors there is something to do */ |
| if (!of |
| || !(of->state & (CLOSE_COMPRESSED |
| | CLOSE_ENCRYPTED | CLOSE_DMTIME))) { |
| res = 0; |
| goto out; |
| } |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) { |
| res = -errno; |
| goto exit; |
| } |
| res = 0; |
| if (of->state & CLOSE_DMTIME) |
| ntfs_inode_update_times(ni,NTFS_UPDATE_MCTIME); |
| if (of->state & CLOSE_COMPRESSED) |
| res = ntfs_attr_pclose(na); |
| #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
| if (of->state & CLOSE_ENCRYPTED) |
| res = ntfs_efs_fixup_attribute(NULL, na); |
| #endif /* HAVE_SETXATTR */ |
| exit: |
| if (na) |
| ntfs_attr_close(na); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| out: |
| /* remove the associate ghost file (even if release failed) */ |
| if (of) { |
| if (of->state & CLOSE_GHOST) { |
| sprintf(ghostname,ghostformat,of->ghost); |
| ntfs_fuse_rm(req, of->parent, ghostname, RM_ANY); |
| } |
| /* remove from open files list */ |
| if (of->next) |
| of->next->previous = of->previous; |
| if (of->previous) |
| of->previous->next = of->next; |
| else |
| ctx->open_files = of->next; |
| free(of); |
| } |
| if (res) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| static void ntfs_fuse_mkdir(fuse_req_t req, fuse_ino_t parent, |
| const char *name, mode_t mode) |
| { |
| int res; |
| struct fuse_entry_param entry; |
| |
| res = ntfs_fuse_create(req, parent, name, S_IFDIR | (mode & 07777), |
| 0, &entry, (char*)NULL, (struct fuse_file_info*)NULL); |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_entry(req, &entry); |
| } |
| |
| static void ntfs_fuse_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) |
| { |
| int res; |
| |
| res = ntfs_fuse_rm(req, parent, name, RM_DIR); |
| if (res) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| static void ntfs_fuse_fsync(fuse_req_t req, |
| fuse_ino_t ino __attribute__((unused)), |
| int type __attribute__((unused)), |
| struct fuse_file_info *fi __attribute__((unused))) |
| { |
| /* sync the full device */ |
| if (ntfs_device_sync(ctx->vol->dev)) |
| fuse_reply_err(req, errno); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| #if defined(FUSE_INTERNAL) || (FUSE_VERSION >= 28) |
| static void ntfs_fuse_ioctl(fuse_req_t req __attribute__((unused)), |
| fuse_ino_t ino __attribute__((unused)), |
| int cmd, void *arg, |
| struct fuse_file_info *fi __attribute__((unused)), |
| unsigned flags, const void *data, |
| size_t in_bufsz, size_t out_bufsz) |
| { |
| ntfs_inode *ni; |
| char *buf = (char*)NULL; |
| int bufsz; |
| int ret = 0; |
| |
| if (flags & FUSE_IOCTL_COMPAT) { |
| ret = -ENOSYS; |
| } else { |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| ret = -errno; |
| goto fail; |
| } |
| bufsz = (in_bufsz > out_bufsz ? in_bufsz : out_bufsz); |
| if (bufsz) { |
| buf = ntfs_malloc(bufsz); |
| if (!buf) { |
| ret = ENOMEM; |
| goto fail; |
| } |
| memcpy(buf, data, in_bufsz); |
| } |
| ret = ntfs_ioctl(ni, cmd, arg, flags, buf); |
| if (ntfs_inode_close (ni)) |
| set_fuse_error(&ret); |
| } |
| if (ret) |
| fail : |
| fuse_reply_err(req, -ret); |
| else |
| fuse_reply_ioctl(req, 0, buf, out_bufsz); |
| if (buf) |
| free(buf); |
| } |
| #endif /* defined(FUSE_INTERNAL) || (FUSE_VERSION >= 28) */ |
| |
| static void ntfs_fuse_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, |
| uint64_t vidx) |
| { |
| ntfs_inode *ni; |
| ntfs_attr *na; |
| LCN lcn; |
| uint64_t lidx = 0; |
| int ret = 0; |
| int cl_per_bl = ctx->vol->cluster_size / blocksize; |
| |
| if (blocksize > ctx->vol->cluster_size) { |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| ret = -errno; |
| goto done; |
| } |
| |
| na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); |
| if (!na) { |
| ret = -errno; |
| goto close_inode; |
| } |
| |
| if ((na->data_flags & (ATTR_COMPRESSION_MASK | ATTR_IS_ENCRYPTED)) |
| || !NAttrNonResident(na)) { |
| ret = -EINVAL; |
| goto close_attr; |
| } |
| |
| if (ntfs_attr_map_whole_runlist(na)) { |
| ret = -errno; |
| goto close_attr; |
| } |
| |
| lcn = ntfs_rl_vcn_to_lcn(na->rl, vidx / cl_per_bl); |
| lidx = (lcn > 0) ? lcn * cl_per_bl + vidx % cl_per_bl : 0; |
| |
| close_attr: |
| ntfs_attr_close(na); |
| close_inode: |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&ret); |
| done : |
| if (ret < 0) |
| fuse_reply_err(req, -ret); |
| else |
| fuse_reply_bmap(req, lidx); |
| } |
| |
| #ifdef HAVE_SETXATTR |
| |
| /* |
| * Name space identifications and prefixes |
| */ |
| |
| enum { |
| XATTRNS_NONE, |
| XATTRNS_USER, |
| XATTRNS_SYSTEM, |
| XATTRNS_SECURITY, |
| XATTRNS_TRUSTED, |
| XATTRNS_OPEN |
| } ; |
| |
| /* |
| * Check whether access to internal data as an extended |
| * attribute in system name space is allowed |
| * |
| * Returns pointer to inode if allowed, |
| * NULL and errno set if not allowed |
| */ |
| |
| static ntfs_inode *ntfs_check_access_xattr(fuse_req_t req, |
| struct SECURITY_CONTEXT *security, |
| fuse_ino_t ino, int attr, BOOL setting) |
| { |
| ntfs_inode *dir_ni; |
| ntfs_inode *ni; |
| BOOL foracl; |
| BOOL bad; |
| mode_t acctype; |
| |
| ni = (ntfs_inode*)NULL; |
| foracl = (attr == XATTR_POSIX_ACC) |
| || (attr == XATTR_POSIX_DEF); |
| /* |
| * When accessing Posix ACL, return unsupported if ACL |
| * were disabled or no user mapping has been defined, |
| * or trying to change a Windows-inherited ACL. |
| * However no error will be returned to getfacl |
| */ |
| if (((!ntfs_fuse_fill_security_context(req, security) |
| || (ctx->secure_flags |
| & ((1 << SECURITY_DEFAULT) | (1 << SECURITY_RAW)))) |
| || !(ctx->secure_flags & (1 << SECURITY_ACL)) |
| || (setting && ctx->inherit)) |
| && foracl) { |
| if (ctx->silent) |
| errno = 0; |
| else |
| errno = EOPNOTSUPP; |
| } else { |
| /* |
| * parent directory must be executable, and |
| * for setting a DOS name it must be writeable |
| */ |
| if (setting && (attr == XATTR_NTFS_DOS_NAME)) |
| acctype = S_IEXEC | S_IWRITE; |
| else |
| acctype = S_IEXEC; |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| /* basic access was checked previously in a lookup */ |
| if (ni && (acctype != S_IEXEC)) { |
| bad = FALSE; |
| /* do not reopen root */ |
| if (ni->mft_no == FILE_root) { |
| /* forbid getting/setting names on root */ |
| if ((attr == XATTR_NTFS_DOS_NAME) |
| || !ntfs_real_allowed_access(security, |
| ni, acctype)) |
| bad = TRUE; |
| } else { |
| dir_ni = ntfs_dir_parent_inode(ni); |
| if (dir_ni) { |
| if (!ntfs_real_allowed_access(security, |
| dir_ni, acctype)) |
| bad = TRUE; |
| if (ntfs_inode_close(dir_ni)) |
| bad = TRUE; |
| } else |
| bad = TRUE; |
| } |
| if (bad) { |
| ntfs_inode_close(ni); |
| ni = (ntfs_inode*)NULL; |
| } |
| } |
| } |
| return (ni); |
| } |
| |
| /* |
| * Determine the name space of an extended attribute |
| */ |
| |
| static int xattr_namespace(const char *name) |
| { |
| int namespace; |
| |
| if (ctx->streams == NF_STREAMS_INTERFACE_XATTR) { |
| namespace = XATTRNS_NONE; |
| if (!strncmp(name, nf_ns_user_prefix, |
| nf_ns_user_prefix_len) |
| && (strlen(name) != (size_t)nf_ns_user_prefix_len)) |
| namespace = XATTRNS_USER; |
| else if (!strncmp(name, nf_ns_system_prefix, |
| nf_ns_system_prefix_len) |
| && (strlen(name) != (size_t)nf_ns_system_prefix_len)) |
| namespace = XATTRNS_SYSTEM; |
| else if (!strncmp(name, nf_ns_security_prefix, |
| nf_ns_security_prefix_len) |
| && (strlen(name) != (size_t)nf_ns_security_prefix_len)) |
| namespace = XATTRNS_SECURITY; |
| else if (!strncmp(name, nf_ns_trusted_prefix, |
| nf_ns_trusted_prefix_len) |
| && (strlen(name) != (size_t)nf_ns_trusted_prefix_len)) |
| namespace = XATTRNS_TRUSTED; |
| } else |
| namespace = XATTRNS_OPEN; |
| return (namespace); |
| } |
| |
| /* |
| * Fix the prefix of an extended attribute |
| */ |
| |
| static int fix_xattr_prefix(const char *name, int namespace, ntfschar **lename) |
| { |
| int len; |
| char *prefixed; |
| |
| *lename = (ntfschar*)NULL; |
| switch (namespace) { |
| case XATTRNS_USER : |
| /* |
| * user name space : remove user prefix |
| */ |
| len = ntfs_mbstoucs(name + nf_ns_user_prefix_len, lename); |
| break; |
| case XATTRNS_SYSTEM : |
| case XATTRNS_SECURITY : |
| case XATTRNS_TRUSTED : |
| /* |
| * security, trusted and unmapped system name spaces : |
| * insert ntfs-3g prefix |
| */ |
| prefixed = (char*)ntfs_malloc(strlen(xattr_ntfs_3g) |
| + strlen(name) + 1); |
| if (prefixed) { |
| strcpy(prefixed,xattr_ntfs_3g); |
| strcat(prefixed,name); |
| len = ntfs_mbstoucs(prefixed, lename); |
| free(prefixed); |
| } else |
| len = -1; |
| break; |
| case XATTRNS_OPEN : |
| /* |
| * in open name space mode : do no fix prefix |
| */ |
| len = ntfs_mbstoucs(name, lename); |
| break; |
| default : |
| len = -1; |
| } |
| return (len); |
| } |
| |
| static void ntfs_fuse_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) |
| { |
| ntfs_attr_search_ctx *actx = NULL; |
| ntfs_inode *ni; |
| char *list = (char*)NULL; |
| int ret = 0; |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| struct SECURITY_CONTEXT security; |
| #endif |
| |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ntfs_fuse_fill_security_context(req, &security); |
| #endif |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| ret = -errno; |
| goto out; |
| } |
| /* Return with no result for symlinks, fifo, etc. */ |
| if (ni->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_REPARSE_POINT)) |
| goto exit; |
| /* otherwise file must be readable */ |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| if (!ntfs_allowed_access(&security,ni,S_IREAD)) { |
| ret = -EACCES; |
| goto exit; |
| } |
| #endif |
| actx = ntfs_attr_get_search_ctx(ni, NULL); |
| if (!actx) { |
| ret = -errno; |
| goto exit; |
| } |
| if (size) { |
| list = (char*)malloc(size); |
| if (!list) { |
| ret = -errno; |
| goto exit; |
| } |
| } |
| |
| if ((ctx->streams == NF_STREAMS_INTERFACE_XATTR) |
| || (ctx->streams == NF_STREAMS_INTERFACE_OPENXATTR)) { |
| ret = ntfs_fuse_listxattr_common(ni, actx, list, size, |
| ctx->streams == NF_STREAMS_INTERFACE_XATTR); |
| if (ret < 0) |
| goto exit; |
| } |
| if (errno != ENOENT) |
| ret = -errno; |
| exit: |
| if (actx) |
| ntfs_attr_put_search_ctx(actx); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&ret); |
| out : |
| if (ret < 0) |
| fuse_reply_err(req, -ret); |
| else |
| if (size) |
| fuse_reply_buf(req, list, ret); |
| else |
| fuse_reply_xattr(req, ret); |
| free(list); |
| } |
| |
| static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, |
| size_t size) |
| { |
| ntfs_inode *ni; |
| ntfs_inode *dir_ni; |
| ntfs_attr *na = NULL; |
| char *value = (char*)NULL; |
| ntfschar *lename = (ntfschar*)NULL; |
| int lename_len; |
| int res; |
| s64 rsize; |
| enum SYSTEMXATTRS attr; |
| int namespace; |
| struct SECURITY_CONTEXT security; |
| |
| attr = ntfs_xattr_system_type(name,ctx->vol); |
| if (attr != XATTR_UNMAPPED) { |
| /* |
| * hijack internal data and ACL retrieval, whatever |
| * mode was selected for xattr (from the user's |
| * point of view, ACLs are not xattr) |
| */ |
| if (size) |
| value = (char*)ntfs_malloc(size); |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| if (!size || value) { |
| ni = ntfs_check_access_xattr(req, &security, ino, |
| attr, FALSE); |
| if (ni) { |
| if (ntfs_allowed_access(&security,ni,S_IREAD)) { |
| if (attr == XATTR_NTFS_DOS_NAME) |
| dir_ni = ntfs_dir_parent_inode(ni); |
| else |
| dir_ni = (ntfs_inode*)NULL; |
| res = ntfs_xattr_system_getxattr(&security, |
| attr, ni, dir_ni, value, size); |
| if (dir_ni && ntfs_inode_close(dir_ni)) |
| set_fuse_error(&res); |
| } else |
| res = -errno; |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } else |
| res = -errno; |
| #else |
| /* |
| * Standard access control has been done by fuse/kernel |
| */ |
| if (!size || value) { |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (ni) { |
| /* user mapping not mandatory */ |
| ntfs_fuse_fill_security_context(req, &security); |
| if (attr == XATTR_NTFS_DOS_NAME) |
| dir_ni = ntfs_dir_parent_inode(ni); |
| else |
| dir_ni = (ntfs_inode*)NULL; |
| res = ntfs_xattr_system_getxattr(&security, |
| attr, ni, dir_ni, value, size); |
| if (dir_ni && ntfs_inode_close(dir_ni)) |
| set_fuse_error(&res); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } else |
| res = -errno; |
| #endif |
| } else |
| res = -errno; |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| if (size) |
| fuse_reply_buf(req, value, res); |
| else |
| fuse_reply_xattr(req, res); |
| free(value); |
| return; |
| } |
| if (ctx->streams == NF_STREAMS_INTERFACE_NONE) { |
| res = -EOPNOTSUPP; |
| goto out; |
| } |
| namespace = xattr_namespace(name); |
| if (namespace == XATTRNS_NONE) { |
| res = -EOPNOTSUPP; |
| goto out; |
| } |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ntfs_fuse_fill_security_context(req,&security); |
| /* trusted only readable by root */ |
| if ((namespace == XATTRNS_TRUSTED) |
| && security.uid) { |
| res = -ENODATA; |
| goto out; |
| } |
| #endif |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto out; |
| } |
| /* Return with no result for symlinks, fifo, etc. */ |
| if (ni->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_REPARSE_POINT)) { |
| res = -ENODATA; |
| goto exit; |
| } |
| /* otherwise file must be readable */ |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| if (!ntfs_allowed_access(&security, ni, S_IREAD)) { |
| res = -errno; |
| goto exit; |
| } |
| #endif |
| lename_len = fix_xattr_prefix(name, namespace, &lename); |
| if (lename_len == -1) { |
| res = -errno; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); |
| if (!na) { |
| res = -ENODATA; |
| goto exit; |
| } |
| rsize = na->data_size; |
| if (ctx->efs_raw |
| && rsize |
| && (na->data_flags & ATTR_IS_ENCRYPTED) |
| && NAttrNonResident(na)) |
| rsize = ((na->data_size + 511) & ~511) + 2; |
| if (size) { |
| if (size >= (size_t)rsize) { |
| value = (char*)ntfs_malloc(rsize); |
| if (value) |
| res = ntfs_attr_pread(na, 0, rsize, value); |
| if (!value || (res != rsize)) |
| res = -errno; |
| } else |
| res = -ERANGE; |
| } else |
| res = rsize; |
| exit: |
| if (na) |
| ntfs_attr_close(na); |
| free(lename); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| |
| out : |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| if (size) |
| fuse_reply_buf(req, value, res); |
| else |
| fuse_reply_xattr(req, res); |
| free(value); |
| } |
| |
| static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, |
| const char *value, size_t size, int flags) |
| { |
| ntfs_inode *ni; |
| ntfs_inode *dir_ni; |
| ntfs_attr *na = NULL; |
| ntfschar *lename = NULL; |
| int res, lename_len; |
| size_t total; |
| s64 part; |
| enum SYSTEMXATTRS attr; |
| int namespace; |
| struct SECURITY_CONTEXT security; |
| |
| attr = ntfs_xattr_system_type(name,ctx->vol); |
| if (attr != XATTR_UNMAPPED) { |
| /* |
| * hijack internal data and ACL setting, whatever |
| * mode was selected for xattr (from the user's |
| * point of view, ACLs are not xattr) |
| * Note : ctime updated on successful settings |
| */ |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ni = ntfs_check_access_xattr(req,&security,ino,attr,TRUE); |
| if (ni) { |
| if (ntfs_allowed_as_owner(&security, ni)) { |
| if (attr == XATTR_NTFS_DOS_NAME) |
| dir_ni = ntfs_dir_parent_inode(ni); |
| else |
| dir_ni = (ntfs_inode*)NULL; |
| res = ntfs_xattr_system_setxattr(&security, |
| attr, ni, dir_ni, value, size, flags); |
| /* never have to close dir_ni */ |
| if (res) |
| res = -errno; |
| } else |
| res = -errno; |
| if (attr != XATTR_NTFS_DOS_NAME) { |
| if (!res) |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } else |
| res = -errno; |
| #else |
| /* creation of a new name is not controlled by fuse */ |
| if (attr == XATTR_NTFS_DOS_NAME) |
| ni = ntfs_check_access_xattr(req, &security, |
| ino, attr, TRUE); |
| else |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (ni) { |
| /* |
| * user mapping is not mandatory |
| * if defined, only owner is allowed |
| */ |
| if (!ntfs_fuse_fill_security_context(req, &security) |
| || ntfs_allowed_as_owner(&security, ni)) { |
| if (attr == XATTR_NTFS_DOS_NAME) |
| dir_ni = ntfs_dir_parent_inode(ni); |
| else |
| dir_ni = (ntfs_inode*)NULL; |
| res = ntfs_xattr_system_setxattr(&security, |
| attr, ni, dir_ni, value, size, flags); |
| /* never have to close dir_ni */ |
| if (res) |
| res = -errno; |
| } else |
| res = -errno; |
| if (attr != XATTR_NTFS_DOS_NAME) { |
| if (!res) |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } else |
| res = -errno; |
| #endif |
| #if CACHEING && !defined(FUSE_INTERNAL) |
| /* |
| * Most of system xattr settings cause changes to some |
| * file attribute (st_mode, st_nlink, st_mtime, etc.), |
| * so we must invalidate cached data when cacheing is |
| * in use (not possible with internal fuse or external |
| * fuse before 2.8) |
| */ |
| if ((res >= 0) |
| && fuse_lowlevel_notify_inval_inode(ctx->fc, ino, -1, 0)) |
| res = -errno; |
| #endif |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| return; |
| } |
| if ((ctx->streams != NF_STREAMS_INTERFACE_XATTR) |
| && (ctx->streams != NF_STREAMS_INTERFACE_OPENXATTR)) { |
| res = -EOPNOTSUPP; |
| goto out; |
| } |
| namespace = xattr_namespace(name); |
| if (namespace == XATTRNS_NONE) { |
| res = -EOPNOTSUPP; |
| goto out; |
| } |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ntfs_fuse_fill_security_context(req,&security); |
| /* security and trusted only settable by root */ |
| if (((namespace == XATTRNS_SECURITY) |
| || (namespace == XATTRNS_TRUSTED)) |
| && security.uid) { |
| res = -EPERM; |
| goto out; |
| } |
| #endif |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto out; |
| } |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| switch (namespace) { |
| case XATTRNS_SECURITY : |
| case XATTRNS_TRUSTED : |
| if (security.uid) { |
| res = -EPERM; |
| goto exit; |
| } |
| break; |
| case XATTRNS_SYSTEM : |
| if (!ntfs_allowed_as_owner(&security, ni)) { |
| res = -EACCES; |
| goto exit; |
| } |
| break; |
| default : |
| /* User xattr not allowed for symlinks, fifo, etc. */ |
| if (ni->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_REPARSE_POINT)) { |
| res = -EPERM; |
| goto exit; |
| } |
| if (!ntfs_allowed_access(&security,ni,S_IWRITE)) { |
| res = -EACCES; |
| goto exit; |
| } |
| break; |
| } |
| #else |
| /* User xattr not allowed for symlinks, fifo, etc. */ |
| if ((namespace == XATTRNS_USER) |
| && (ni->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_REPARSE_POINT))) { |
| res = -EPERM; |
| goto exit; |
| } |
| #endif |
| lename_len = fix_xattr_prefix(name, namespace, &lename); |
| if ((lename_len == -1) |
| || (ctx->windows_names |
| && ntfs_forbidden_chars(lename,lename_len))) { |
| res = -errno; |
| goto exit; |
| } |
| na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); |
| if (na && flags == XATTR_CREATE) { |
| res = -EEXIST; |
| goto exit; |
| } |
| if (!na) { |
| if (flags == XATTR_REPLACE) { |
| res = -ENODATA; |
| goto exit; |
| } |
| if (ntfs_attr_add(ni, AT_DATA, lename, lename_len, NULL, 0)) { |
| res = -errno; |
| goto exit; |
| } |
| if (!(ni->flags & FILE_ATTR_ARCHIVE)) { |
| set_archive(ni); |
| NInoFileNameSetDirty(ni); |
| } |
| na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); |
| if (!na) { |
| res = -errno; |
| goto exit; |
| } |
| } else { |
| /* currently compressed streams can only be wiped out */ |
| if (ntfs_attr_truncate(na, (s64)0 /* size */)) { |
| res = -errno; |
| goto exit; |
| } |
| } |
| total = 0; |
| res = 0; |
| if (size) { |
| do { |
| part = ntfs_attr_pwrite(na, total, size - total, |
| &value[total]); |
| if (part > 0) |
| total += part; |
| } while ((part > 0) && (total < size)); |
| } |
| if ((total != size) || ntfs_attr_pclose(na)) |
| res = -errno; |
| else { |
| if (ctx->efs_raw |
| && (ni->flags & FILE_ATTR_ENCRYPTED)) { |
| if (ntfs_efs_fixup_attribute(NULL,na)) |
| res = -errno; |
| } |
| } |
| if (!res) { |
| ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); |
| if (!(ni->flags & FILE_ATTR_ARCHIVE)) { |
| set_archive(ni); |
| NInoFileNameSetDirty(ni); |
| } |
| } |
| exit: |
| if (na) |
| ntfs_attr_close(na); |
| free(lename); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| out : |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| } |
| |
| static void ntfs_fuse_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) |
| { |
| ntfs_inode *ni; |
| ntfs_inode *dir_ni; |
| ntfschar *lename = NULL; |
| int res = 0, lename_len; |
| enum SYSTEMXATTRS attr; |
| int namespace; |
| struct SECURITY_CONTEXT security; |
| |
| attr = ntfs_xattr_system_type(name,ctx->vol); |
| if (attr != XATTR_UNMAPPED) { |
| switch (attr) { |
| /* |
| * Removal of NTFS ACL, ATTRIB, EFSINFO or TIMES |
| * is never allowed |
| */ |
| case XATTR_NTFS_ACL : |
| case XATTR_NTFS_ATTRIB : |
| case XATTR_NTFS_ATTRIB_BE : |
| case XATTR_NTFS_EFSINFO : |
| case XATTR_NTFS_TIMES : |
| case XATTR_NTFS_TIMES_BE : |
| case XATTR_NTFS_CRTIME : |
| case XATTR_NTFS_CRTIME_BE : |
| res = -EPERM; |
| break; |
| default : |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ni = ntfs_check_access_xattr(req, &security, ino, |
| attr,TRUE); |
| if (ni) { |
| if (ntfs_allowed_as_owner(&security, ni)) { |
| if (attr == XATTR_NTFS_DOS_NAME) |
| dir_ni = ntfs_dir_parent_inode(ni); |
| else |
| dir_ni = (ntfs_inode*)NULL; |
| res = ntfs_xattr_system_removexattr(&security, |
| attr, ni, dir_ni); |
| if (res) |
| res = -errno; |
| /* never have to close dir_ni */ |
| } else |
| res = -errno; |
| if (attr != XATTR_NTFS_DOS_NAME) { |
| if (!res) |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } else |
| res = -errno; |
| #else |
| /* creation of a new name is not controlled by fuse */ |
| if (attr == XATTR_NTFS_DOS_NAME) |
| ni = ntfs_check_access_xattr(req, &security, |
| ino, attr, TRUE); |
| else |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (ni) { |
| /* |
| * user mapping is not mandatory |
| * if defined, only owner is allowed |
| */ |
| if (!ntfs_fuse_fill_security_context(req, &security) |
| || ntfs_allowed_as_owner(&security, ni)) { |
| if (attr == XATTR_NTFS_DOS_NAME) |
| dir_ni = ntfs_dir_parent_inode(ni); |
| else |
| dir_ni = (ntfs_inode*)NULL; |
| res = ntfs_xattr_system_removexattr(&security, |
| attr, ni, dir_ni); |
| /* never have to close dir_ni */ |
| if (res) |
| res = -errno; |
| } else |
| res = -errno; |
| if (attr != XATTR_NTFS_DOS_NAME) { |
| if (!res) |
| ntfs_fuse_update_times(ni, |
| NTFS_UPDATE_CTIME); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| } |
| } else |
| res = -errno; |
| #endif |
| #if CACHEING && !defined(FUSE_INTERNAL) |
| /* |
| * Some allowed system xattr removals cause changes to |
| * some file attribute (st_mode, st_nlink, etc.), |
| * so we must invalidate cached data when cacheing is |
| * in use (not possible with internal fuse or external |
| * fuse before 2.8) |
| */ |
| if ((res >= 0) |
| && fuse_lowlevel_notify_inval_inode(ctx->fc, |
| ino, -1, 0)) |
| res = -errno; |
| #endif |
| break; |
| } |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| return; |
| } |
| if ((ctx->streams != NF_STREAMS_INTERFACE_XATTR) |
| && (ctx->streams != NF_STREAMS_INTERFACE_OPENXATTR)) { |
| res = -EOPNOTSUPP; |
| goto out; |
| } |
| namespace = xattr_namespace(name); |
| if (namespace == XATTRNS_NONE) { |
| res = -EOPNOTSUPP; |
| goto out; |
| } |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| ntfs_fuse_fill_security_context(req,&security); |
| /* security and trusted only settable by root */ |
| if (((namespace == XATTRNS_SECURITY) |
| || (namespace == XATTRNS_TRUSTED)) |
| && security.uid) { |
| res = -EACCES; |
| goto out; |
| } |
| #endif |
| ni = ntfs_inode_open(ctx->vol, INODE(ino)); |
| if (!ni) { |
| res = -errno; |
| goto out; |
| } |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| switch (namespace) { |
| case XATTRNS_SECURITY : |
| case XATTRNS_TRUSTED : |
| if (security.uid) { |
| res = -EPERM; |
| goto exit; |
| } |
| break; |
| case XATTRNS_SYSTEM : |
| if (!ntfs_allowed_as_owner(&security, ni)) { |
| res = -EACCES; |
| goto exit; |
| } |
| break; |
| default : |
| /* User xattr not allowed for symlinks, fifo, etc. */ |
| if (ni->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_REPARSE_POINT)) { |
| res = -EPERM; |
| goto exit; |
| } |
| if (!ntfs_allowed_access(&security,ni,S_IWRITE)) { |
| res = -EACCES; |
| goto exit; |
| } |
| break; |
| } |
| #else |
| /* User xattr not allowed for symlinks, fifo, etc. */ |
| if ((namespace == XATTRNS_USER) |
| && (ni->flags & (FILE_ATTR_SYSTEM | FILE_ATTR_REPARSE_POINT))) { |
| res = -EPERM; |
| goto exit; |
| } |
| #endif |
| lename_len = fix_xattr_prefix(name, namespace, &lename); |
| if (lename_len == -1) { |
| res = -errno; |
| goto exit; |
| } |
| if (ntfs_attr_remove(ni, AT_DATA, lename, lename_len)) { |
| if (errno == ENOENT) |
| errno = ENODATA; |
| res = -errno; |
| } |
| if (!res) { |
| ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); |
| if (!(ni->flags & FILE_ATTR_ARCHIVE)) { |
| set_archive(ni); |
| NInoFileNameSetDirty(ni); |
| } |
| } |
| exit: |
| free(lename); |
| if (ntfs_inode_close(ni)) |
| set_fuse_error(&res); |
| out : |
| if (res < 0) |
| fuse_reply_err(req, -res); |
| else |
| fuse_reply_err(req, 0); |
| return; |
| |
| } |
| |
| #else |
| #if POSIXACLS |
| #error "Option inconsistency : POSIXACLS requires SETXATTR" |
| #endif |
| #endif /* HAVE_SETXATTR */ |
| |
| static void ntfs_close(void) |
| { |
| struct SECURITY_CONTEXT security; |
| |
| if (!ctx) |
| return; |
| |
| if (!ctx->vol) |
| return; |
| |
| if (ctx->mounted) { |
| ntfs_log_info("Unmounting %s (%s)\n", opts.device, |
| ctx->vol->vol_name); |
| if (ntfs_fuse_fill_security_context((fuse_req_t)NULL, &security)) { |
| if (ctx->seccache && ctx->seccache->head.p_reads) { |
| ntfs_log_info("Permissions cache : %lu writes, " |
| "%lu reads, %lu.%1lu%% hits\n", |
| ctx->seccache->head.p_writes, |
| ctx->seccache->head.p_reads, |
| 100 * ctx->seccache->head.p_hits |
| / ctx->seccache->head.p_reads, |
| 1000 * ctx->seccache->head.p_hits |
| / ctx->seccache->head.p_reads % 10); |
| } |
| } |
| ntfs_close_secure(&security); |
| } |
| |
| if (ntfs_umount(ctx->vol, FALSE)) |
| ntfs_log_perror("Failed to close volume %s", opts.device); |
| |
| ctx->vol = NULL; |
| } |
| |
| static void ntfs_fuse_destroy2(void *notused __attribute__((unused))) |
| { |
| ntfs_close(); |
| } |
| |
| static struct fuse_lowlevel_ops ntfs_3g_ops = { |
| .lookup = ntfs_fuse_lookup, |
| .getattr = ntfs_fuse_getattr, |
| .readlink = ntfs_fuse_readlink, |
| .opendir = ntfs_fuse_opendir, |
| .readdir = ntfs_fuse_readdir, |
| .releasedir = ntfs_fuse_releasedir, |
| .open = ntfs_fuse_open, |
| .release = ntfs_fuse_release, |
| .read = ntfs_fuse_read, |
| .write = ntfs_fuse_write, |
| .setattr = ntfs_fuse_setattr, |
| .statfs = ntfs_fuse_statfs, |
| .create = ntfs_fuse_create_file, |
| .mknod = ntfs_fuse_mknod, |
| .symlink = ntfs_fuse_symlink, |
| .link = ntfs_fuse_link, |
| .unlink = ntfs_fuse_unlink, |
| .rename = ntfs_fuse_rename, |
| .mkdir = ntfs_fuse_mkdir, |
| .rmdir = ntfs_fuse_rmdir, |
| .fsync = ntfs_fuse_fsync, |
| .fsyncdir = ntfs_fuse_fsync, |
| .bmap = ntfs_fuse_bmap, |
| .destroy = ntfs_fuse_destroy2, |
| #if defined(FUSE_INTERNAL) || (FUSE_VERSION >= 28) |
| .ioctl = ntfs_fuse_ioctl, |
| #endif /* defined(FUSE_INTERNAL) || (FUSE_VERSION >= 28) */ |
| #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) |
| .access = ntfs_fuse_access, |
| #endif |
| #ifdef HAVE_SETXATTR |
| .getxattr = ntfs_fuse_getxattr, |
| .setxattr = ntfs_fuse_setxattr, |
| .removexattr = ntfs_fuse_removexattr, |
| .listxattr = ntfs_fuse_listxattr, |
| #endif /* HAVE_SETXATTR */ |
| #if 0 && (defined(__APPLE__) || defined(__DARWIN__)) /* Unfinished. */ |
| /* MacFUSE extensions. */ |
| .getxtimes = ntfs_macfuse_getxtimes, |
| .setcrtime = ntfs_macfuse_setcrtime, |
| .setbkuptime = ntfs_macfuse_setbkuptime, |
| .setchgtime = ntfs_macfuse_setchgtime, |
| #endif /* defined(__APPLE__) || defined(__DARWIN__) */ |
| .init = ntfs_init |
| }; |
| |
| static int ntfs_fuse_init(void) |
| { |
| ctx = (ntfs_fuse_context_t*)ntfs_calloc(sizeof(ntfs_fuse_context_t)); |
| if (!ctx) |
| return -1; |
| |
| *ctx = (ntfs_fuse_context_t) { |
| .uid = getuid(), |
| .gid = getgid(), |
| #if defined(linux) |
| .streams = NF_STREAMS_INTERFACE_XATTR, |
| #else |
| .streams = NF_STREAMS_INTERFACE_NONE, |
| #endif |
| .atime = ATIME_RELATIVE, |
| .silent = TRUE, |
| .recover = TRUE |
| }; |
| return 0; |
| } |
| |
| static int ntfs_open(const char *device) |
| { |
| unsigned long flags = 0; |
| ntfs_volume *vol; |
| |
| if (!ctx->blkdev) |
| flags |= NTFS_MNT_EXCLUSIVE; |
| if (ctx->ro) |
| flags |= NTFS_MNT_RDONLY; |
| if (ctx->recover) |
| flags |= NTFS_MNT_RECOVER; |
| if (ctx->hiberfile) |
| flags |= NTFS_MNT_IGNORE_HIBERFILE; |
| |
| ctx->vol = vol = ntfs_mount(device, flags); |
| if (!vol) { |
| ntfs_log_perror("Failed to mount '%s'", device); |
| goto err_out; |
| } |
| if (ctx->sync && ctx->vol->dev) |
| NDevSetSync(ctx->vol->dev); |
| if (ctx->compression) |
| NVolSetCompression(ctx->vol); |
| else |
| NVolClearCompression(ctx->vol); |
| #ifdef HAVE_SETXATTR |
| /* archivers must see hidden files */ |
| if (ctx->efs_raw) |
| ctx->hide_hid_files = FALSE; |
| #endif |
| if (ntfs_set_shown_files(ctx->vol, ctx->show_sys_files, |
| !ctx->hide_hid_files, ctx->hide_dot_files)) |
| goto err_out; |
| |
| if (ctx->ignore_case && ntfs_set_ignore_case(vol)) |
| goto err_out; |
| |
| vol->free_clusters = ntfs_attr_get_free_bits(vol->lcnbmp_na); |
| if (vol->free_clusters < 0) { |
| ntfs_log_perror("Failed to read NTFS $Bitmap"); |
| goto err_out; |
| } |
| |
| vol->free_mft_records = ntfs_get_nr_free_mft_records(vol); |
| if (vol->free_mft_records < 0) { |
| ntfs_log_perror("Failed to calculate free MFT records"); |
| goto err_out; |
| } |
| |
| if (ctx->hiberfile && ntfs_volume_check_hiberfile(vol, 0)) { |
| if (errno != EPERM) |
| goto err_out; |
| if (ntfs_fuse_rm((fuse_req_t)NULL,FILE_root,"hiberfil.sys", |
| RM_LINK)) |
| goto err_out; |
| } |
| |
| errno = 0; |
| err_out: |
| return ntfs_volume_error(errno); |
| |
| } |
| |
| static void usage(void) |
| { |
| ntfs_log_info(usage_msg, EXEC_NAME, VERSION, FUSE_TYPE, fuse_version(), |
| 5 + POSIXACLS*6 - KERNELPERMS*3 + CACHEING, |
| EXEC_NAME, ntfs_home); |
| } |
| |
| #if defined(linux) || defined(__uClinux__) |
| |
| static const char *dev_fuse_msg = |
| "HINT: You should be root, or make ntfs-3g setuid root, or load the FUSE\n" |
| " kernel module as root ('modprobe fuse' or 'insmod <path_to>/fuse.ko'" |
| " or insmod <path_to>/fuse.o'). Make also sure that the fuse device" |
| " exists. It's usually either /dev/fuse or /dev/misc/fuse."; |
| |
| static const char *fuse26_kmod_msg = |
| "WARNING: Deficient Linux kernel detected. Some driver features are\n" |
| " not available (swap file on NTFS, boot from NTFS by LILO), and\n" |
| " unmount is not safe unless it's made sure the ntfs-3g process\n" |
| " naturally terminates after calling 'umount'. If you wish this\n" |
| " message to disappear then you should upgrade to at least kernel\n" |
| " version 2.6.20, or request help from your distribution to fix\n" |
| " the kernel problem. The below web page has more information:\n" |
| " http://tuxera.com/community/ntfs-3g-faq/#fuse26\n" |
| "\n"; |
| |
| static void mknod_dev_fuse(const char *dev) |
| { |
| struct stat st; |
| |
| if (stat(dev, &st) && (errno == ENOENT)) { |
| mode_t mask = umask(0); |
| if (mknod(dev, S_IFCHR | 0666, makedev(10, 229))) { |
| ntfs_log_perror("Failed to create '%s'", dev); |
| if (errno == EPERM) |
| ntfs_log_error("%s", dev_fuse_msg); |
| } |
| umask(mask); |
| } |
| } |
| |
| static void create_dev_fuse(void) |
| { |
| mknod_dev_fuse("/dev/fuse"); |
| |
| #ifdef __UCLIBC__ |
| { |
| struct stat st; |
| /* The fuse device is under /dev/misc using devfs. */ |
| if (stat("/dev/misc", &st) && (errno == ENOENT)) { |
| mode_t mask = umask(0); |
| mkdir("/dev/misc", 0775); |
| umask(mask); |
| } |
| mknod_dev_fuse("/dev/misc/fuse"); |
| } |
| #endif |
| } |
| |
| static fuse_fstype get_fuse_fstype(void) |
| { |
| char buf[256]; |
| fuse_fstype fstype = FSTYPE_NONE; |
| |
| FILE *f = fopen("/proc/filesystems", "r"); |
| if (!f) { |
| ntfs_log_perror("Failed to open /proc/filesystems"); |
| return FSTYPE_UNKNOWN; |
| } |
| |
| while (fgets(buf, sizeof(buf), f)) { |
| if (strstr(buf, "fuseblk\n")) { |
| fstype = FSTYPE_FUSEBLK; |
| break; |
| } |
| if (strstr(buf, "fuse\n")) |
| fstype = FSTYPE_FUSE; |
| } |
| |
| fclose(f); |
| return fstype; |
| } |
| |
| static fuse_fstype load_fuse_module(void) |
| { |
| int i; |
| struct stat st; |
| pid_t pid; |
| const char *cmd = "/sbin/modprobe"; |
| struct timespec req = { 0, 100000000 }; /* 100 msec */ |
| fuse_fstype fstype; |
| |
| if (!stat(cmd, &st) && !geteuid()) { |
| pid = fork(); |
| if (!pid) { |
| execl(cmd, cmd, "fuse", NULL); |
| _exit(1); |
| } else if (pid != -1) |
| waitpid(pid, NULL, 0); |
| } |
| |
| for (i = 0; i < 10; i++) { |
| /* |
| * We sleep first because despite the detection of the loaded |
| * FUSE kernel module, fuse_mount() can still fail if it's not |
| * fully functional/initialized. Note, of course this is still |
| * unreliable but usually helps. |
| */ |
| nanosleep(&req, NULL); |
| fstype = get_fuse_fstype(); |
| if (fstype != FSTYPE_NONE) |
| break; |
| } |
| return fstype; |
| } |
| |
| #endif |
| |
| static struct fuse_chan *try_fuse_mount(char *parsed_options) |
| { |
| struct fuse_chan *fc = NULL; |
| struct fuse_args margs = FUSE_ARGS_INIT(0, NULL); |
| |
| /* The fuse_mount() options get modified, so we always rebuild it */ |
| if ((fuse_opt_add_arg(&margs, EXEC_NAME) == -1 || |
| fuse_opt_add_arg(&margs, "-o") == -1 || |
| fuse_opt_add_arg(&margs, parsed_options) == -1)) { |
| ntfs_log_error("Failed to set FUSE options.\n"); |
| goto free_args; |
| } |
| |
| fc = fuse_mount(opts.mnt_point, &margs); |
| free_args: |
| fuse_opt_free_args(&margs); |
| return fc; |
| |
| } |
| |
| static int set_fuseblk_options(char **parsed_options) |
| { |
| char options[64]; |
| long pagesize; |
| u32 blksize = ctx->vol->cluster_size; |
| |
| pagesize = sysconf(_SC_PAGESIZE); |
| if (pagesize < 1) |
| pagesize = 4096; |
| |
| if (blksize > (u32)pagesize) |
| blksize = pagesize; |
| |
| snprintf(options, sizeof(options), ",blkdev,blksize=%u", blksize); |
| if (ntfs_strappend(parsed_options, options)) |
| return -1; |
| return 0; |
| } |
| |
| static struct fuse_session *mount_fuse(char *parsed_options) |
| { |
| struct fuse_session *se = NULL; |
| struct fuse_args args = FUSE_ARGS_INIT(0, NULL); |
| |
| ctx->fc = try_fuse_mount(parsed_options); |
| if (!ctx->fc) |
| return NULL; |
| |
| if (fuse_opt_add_arg(&args, "") == -1) |
| goto err; |
| if (ctx->debug) |
| if (fuse_opt_add_arg(&args, "-odebug") == -1) |
| goto err; |
| |
| se = fuse_lowlevel_new(&args , &ntfs_3g_ops, sizeof(ntfs_3g_ops), NULL); |
| if (!se) |
| goto err; |
| |
| |
| if (fuse_set_signal_handlers(se)) |
| goto err_destroy; |
| fuse_session_add_chan(se, ctx->fc); |
| out: |
| fuse_opt_free_args(&args); |
| return se; |
| err_destroy: |
| fuse_session_destroy(se); |
| se = NULL; |
| err: |
| fuse_unmount(opts.mnt_point, ctx->fc); |
| goto out; |
| } |
| |
| static void setup_logging(char *parsed_options) |
| { |
| if (!ctx->no_detach) { |
| if (daemon(0, ctx->debug)) |
| ntfs_log_error("Failed to daemonize.\n"); |
| else if (!ctx->debug) { |
| #ifndef DEBUG |
| ntfs_log_set_handler(ntfs_log_handler_syslog); |
| /* Override default libntfs identify. */ |
| openlog(EXEC_NAME, LOG_PID, LOG_DAEMON); |
| #endif |
| } |
| } |
| |
| ctx->seccache = (struct PERMISSIONS_CACHE*)NULL; |
| |
| ntfs_log_info("Version %s %s %d\n", VERSION, FUSE_TYPE, fuse_version()); |
| if (strcmp(opts.arg_device,opts.device)) |
| ntfs_log_info("Requested device %s canonicalized as %s\n", |
| opts.arg_device,opts.device); |
| ntfs_log_info("Mounted %s (%s, label \"%s\", NTFS %d.%d)\n", |
| opts.device, (ctx->ro) ? "Read-Only" : "Read-Write", |
| ctx->vol->vol_name, ctx->vol->major_ver, |
| ctx->vol->minor_ver); |
| ntfs_log_info("Cmdline options: %s\n", opts.options ? opts.options : ""); |
| ntfs_log_info("Mount options: %s\n", parsed_options); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| char *parsed_options = NULL; |
| struct fuse_session *se; |
| #if !(defined(__sun) && defined (__SVR4)) |
| fuse_fstype fstype = FSTYPE_UNKNOWN; |
| #endif |
| const char *permissions_mode = (const char*)NULL; |
| const char *failed_secure = (const char*)NULL; |
| #if defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) |
| struct XATTRMAPPING *xattr_mapping = (struct XATTRMAPPING*)NULL; |
| #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ |
| struct stat sbuf; |
| unsigned long existing_mount; |
| int err, fd; |
| |
| /* |
| * Make sure file descriptors 0, 1 and 2 are open, |
| * otherwise chaos would ensue. |
| */ |
| do { |
| fd = open("/dev/null", O_RDWR); |
| if (fd > 2) |
| close(fd); |
| } while (fd >= 0 && fd <= 2); |
| |
| #ifndef FUSE_INTERNAL |
| if ((getuid() != geteuid()) || (getgid() != getegid())) { |
| fprintf(stderr, "%s", setuid_msg); |
| return NTFS_VOLUME_INSECURE; |
| } |
| #endif |
| if (drop_privs()) |
| return NTFS_VOLUME_NO_PRIVILEGE; |
| |
| ntfs_set_locale(); |
| ntfs_log_set_handler(ntfs_log_handler_stderr); |
| |
| if (ntfs_parse_options(&opts, usage, argc, argv)) { |
| usage(); |
| return NTFS_VOLUME_SYNTAX_ERROR; |
| } |
| |
| if (ntfs_fuse_init()) { |
| err = NTFS_VOLUME_OUT_OF_MEMORY; |
| goto err2; |
| } |
| |
| parsed_options = parse_mount_options(ctx, &opts, TRUE); |
| if (!parsed_options) { |
| err = NTFS_VOLUME_SYNTAX_ERROR; |
| goto err_out; |
| } |
| if (!ntfs_check_if_mounted(opts.device,&existing_mount) |
| && (existing_mount & NTFS_MF_MOUNTED) |
| /* accept multiple read-only mounts */ |
| && (!(existing_mount & NTFS_MF_READONLY) || !ctx->ro)) { |
| err = NTFS_VOLUME_LOCKED; |
| goto err_out; |
| } |
| |
| /* need absolute mount point for junctions */ |
| if (opts.mnt_point[0] == '/') |
| ctx->abs_mnt_point = strdup(opts.mnt_point); |
| else { |
| ctx->abs_mnt_point = (char*)ntfs_malloc(PATH_MAX); |
| if (ctx->abs_mnt_point) { |
| if (getcwd(ctx->abs_mnt_point, |
| PATH_MAX - strlen(opts.mnt_point) - 1)) { |
| strcat(ctx->abs_mnt_point, "/"); |
| strcat(ctx->abs_mnt_point, opts.mnt_point); |
| #if defined(__sun) && defined (__SVR4) |
| /* Solaris also wants the absolute mount point */ |
| opts.mnt_point = ctx->abs_mnt_point; |
| #endif /* defined(__sun) && defined (__SVR4) */ |
| } |
| } |
| } |
| if (!ctx->abs_mnt_point) { |
| err = NTFS_VOLUME_OUT_OF_MEMORY; |
| goto err_out; |
| } |
| |
| ctx->security.uid = 0; |
| ctx->security.gid = 0; |
| if ((opts.mnt_point[0] == '/') |
| && !stat(opts.mnt_point,&sbuf)) { |
| /* collect owner of mount point, useful for default mapping */ |
| ctx->security.uid = sbuf.st_uid; |
| ctx->security.gid = sbuf.st_gid; |
| } |
| |
| #if defined(linux) || defined(__uClinux__) |
| fstype = get_fuse_fstype(); |
| |
| err = NTFS_VOLUME_NO_PRIVILEGE; |
| if (restore_privs()) |
| goto err_out; |
| |
| if (fstype == FSTYPE_NONE || fstype == FSTYPE_UNKNOWN) |
| fstype = load_fuse_module(); |
| create_dev_fuse(); |
| |
| if (drop_privs()) |
| goto err_out; |
| #endif |
| if (stat(opts.device, &sbuf)) { |
| ntfs_log_perror("Failed to access '%s'", opts.device); |
| err = NTFS_VOLUME_NO_PRIVILEGE; |
| goto err_out; |
| } |
| |
| #if !(defined(__sun) && defined (__SVR4)) |
| /* Always use fuseblk for block devices unless it's surely missing. */ |
| if (S_ISBLK(sbuf.st_mode) && (fstype != FSTYPE_FUSE)) |
| ctx->blkdev = TRUE; |
| #endif |
| |
| #ifndef FUSE_INTERNAL |
| if (getuid() && ctx->blkdev) { |
| ntfs_log_error("%s", unpriv_fuseblk_msg); |
| err = NTFS_VOLUME_NO_PRIVILEGE; |
| goto err2; |
| } |
| #endif |
| err = ntfs_open(opts.device); |
| if (err) |
| goto err_out; |
| |
| /* Force read-only mount if the device was found read-only */ |
| if (!ctx->ro && NVolReadOnly(ctx->vol)) { |
| ctx->ro = TRUE; |
| if (ntfs_strinsert(&parsed_options, ",ro")) |
| goto err_out; |
| } |
| /* We must do this after ntfs_open() to be able to set the blksize */ |
| if (ctx->blkdev && set_fuseblk_options(&parsed_options)) |
| goto err_out; |
| |
| ctx->security.vol = ctx->vol; |
| ctx->vol->secure_flags = ctx->secure_flags; |
| #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
| ctx->vol->efs_raw = ctx->efs_raw; |
| #endif /* HAVE_SETXATTR */ |
| /* JPA open $Secure, (whatever NTFS version !) */ |
| /* to initialize security data */ |
| if (ntfs_open_secure(ctx->vol) && (ctx->vol->major_ver >= 3)) |
| failed_secure = "Could not open file $Secure"; |
| if (!ntfs_build_mapping(&ctx->security,ctx->usermap_path, |
| (ctx->vol->secure_flags |
| & ((1 << SECURITY_DEFAULT) | (1 << SECURITY_ACL))) |
| && !ctx->inherit |
| && !(ctx->vol->secure_flags & (1 << SECURITY_WANTED)))) { |
| #if POSIXACLS |
| /* use basic permissions if requested */ |
| if (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT)) |
| permissions_mode = "User mapping built, Posix ACLs not used"; |
| else { |
| permissions_mode = "User mapping built, Posix ACLs in use"; |
| #if KERNELACLS |
| if (ntfs_strinsert(&parsed_options, |
| ",default_permissions,acl")) { |
| err = NTFS_VOLUME_SYNTAX_ERROR; |
| goto err_out; |
| } |
| #endif /* KERNELACLS */ |
| } |
| #else /* POSIXACLS */ |
| if (!(ctx->vol->secure_flags |
| & ((1 << SECURITY_DEFAULT) | (1 << SECURITY_ACL)))) { |
| /* |
| * No explicit option but user mapping found |
| * force default security |
| */ |
| #if KERNELPERMS |
| ctx->vol->secure_flags |= (1 << SECURITY_DEFAULT); |
| if (ntfs_strinsert(&parsed_options, ",default_permissions")) { |
| err = NTFS_VOLUME_SYNTAX_ERROR; |
| goto err_out; |
| } |
| #endif /* KERNELPERMS */ |
| } |
| permissions_mode = "User mapping built"; |
| #endif /* POSIXACLS */ |
| ctx->dmask = ctx->fmask = 0; |
| } else { |
| ctx->security.uid = ctx->uid; |
| ctx->security.gid = ctx->gid; |
| /* same ownership/permissions for all files */ |
| ctx->security.mapping[MAPUSERS] = (struct MAPPING*)NULL; |
| ctx->security.mapping[MAPGROUPS] = (struct MAPPING*)NULL; |
| if ((ctx->vol->secure_flags & (1 << SECURITY_WANTED)) |
| && !(ctx->vol->secure_flags & (1 << SECURITY_DEFAULT))) { |
| ctx->vol->secure_flags |= (1 << SECURITY_DEFAULT); |
| if (ntfs_strinsert(&parsed_options, ",default_permissions")) { |
| err = NTFS_VOLUME_SYNTAX_ERROR; |
| goto err_out; |
| } |
| } |
| if (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT)) { |
| ctx->vol->secure_flags |= (1 << SECURITY_RAW); |
| permissions_mode = "Global ownership and permissions enforced"; |
| } else { |
| ctx->vol->secure_flags &= ~(1 << SECURITY_RAW); |
| permissions_mode = "Ownership and permissions disabled"; |
| } |
| } |
| if (ctx->usermap_path) |
| free (ctx->usermap_path); |
| |
| #if defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) |
| xattr_mapping = ntfs_xattr_build_mapping(ctx->vol, |
| ctx->xattrmap_path); |
| ctx->vol->xattr_mapping = xattr_mapping; |
| /* |
| * Errors are logged, do not refuse mounting, it would be |
| * too difficult to fix the unmountable mapping file. |
| */ |
| if (ctx->xattrmap_path) |
| free(ctx->xattrmap_path); |
| #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ |
| |
| se = mount_fuse(parsed_options); |
| if (!se) { |
| err = NTFS_VOLUME_FUSE_ERROR; |
| goto err_out; |
| } |
| |
| ctx->mounted = TRUE; |
| |
| #if defined(linux) || defined(__uClinux__) |
| if (S_ISBLK(sbuf.st_mode) && (fstype == FSTYPE_FUSE)) |
| ntfs_log_info("%s", fuse26_kmod_msg); |
| #endif |
| setup_logging(parsed_options); |
| if (failed_secure) |
| ntfs_log_info("%s\n",failed_secure); |
| if (permissions_mode) |
| ntfs_log_info("%s, configuration type %d\n",permissions_mode, |
| 5 + POSIXACLS*6 - KERNELPERMS*3 + CACHEING); |
| |
| fuse_session_loop(se); |
| fuse_remove_signal_handlers(se); |
| |
| err = 0; |
| |
| fuse_unmount(opts.mnt_point, ctx->fc); |
| fuse_session_destroy(se); |
| err_out: |
| ntfs_mount_error(opts.device, opts.mnt_point, err); |
| if (ctx->abs_mnt_point) |
| free(ctx->abs_mnt_point); |
| #if defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) |
| ntfs_xattr_free_mapping(xattr_mapping); |
| #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ |
| err2: |
| ntfs_close(); |
| free(ctx); |
| free(parsed_options); |
| free(opts.options); |
| free(opts.device); |
| return err; |
| } |
| |