diff --git a/README b/README index 0f6a7db2..daf5ccf2 100644 --- a/README +++ b/README @@ -16,8 +16,10 @@ reliability and feature richness per invested effort wise. Besides the common file system features, NTFS-3G has support for file ownership and permissions, POSIX ACLs, junction points, extended attributes -and creating compressed files. Parameter files in the directory .NTFS-3G may -be required to enable them, please get the instructions from +and creating internally compressed files (parameter files in the directory +.NTFS-3G may be required to enable them). The new compressed file formats +available in Windows 10 can also be read through a plugin. For using +advanced features, please get the instructions from http://www.tuxera.com/community/ntfs-3g-advanced/ @@ -59,6 +61,7 @@ typing : Below are a few specific options to ./configure : --disable-ntfsprogs : do not build the ntfsprogs tools, + --disable-plugins : disable support for plugins --enable-posix-acls : enable support for Posix ACLs --enable-xattr-mappings : enable system extended attributes mappings --with-fuse=external : use external fuse (overriding Linux default) diff --git a/configure.ac b/configure.ac index d31a9cda..b14b00af 100644 --- a/configure.ac +++ b/configure.ac @@ -121,6 +121,14 @@ AC_ARG_ENABLE( [enable_xattr_mappings="no"] ) +AC_ARG_ENABLE( + [plugins], + [AS_HELP_STRING([--disable-plugins], [Disable external reparse point + plugins for the ntfs-3g FUSE driver])], + , + [disable_plugins="no"] +) + AC_ARG_ENABLE( [device-default-io-ops], [AS_HELP_STRING([--disable-device-default-io-ops],[install default IO ops])], @@ -566,6 +574,7 @@ test "${enable_device_default_io_ops}" = "no" && AC_DEFINE( test "${enable_mtab}" = "no" && AC_DEFINE([IGNORE_MTAB], [1], [Don't update /etc/mtab]) test "${enable_posix_acls}" != "no" && AC_DEFINE([POSIXACLS], [1], [POSIX ACL support]) test "${enable_xattr_mappings}" != "no" && AC_DEFINE([XATTR_MAPPINGS], [1], [system extended attributes mappings]) +test "${disable_plugins}" != "no" && AC_DEFINE([DISABLE_PLUGINS], [1], [Define to 1 for disabling reparse plugins]) test "${enable_really_static}" = "yes" && enable_library="no" test "${enable_library}" = "no" && enable_ldconfig="no" @@ -617,6 +626,7 @@ AM_CONDITIONAL([ENABLE_NTFS_3G], [test "${enable_ntfs_3g}" = "yes"]) AM_CONDITIONAL([ENABLE_NTFSPROGS], [test "${enable_ntfsprogs}" = "yes"]) AM_CONDITIONAL([ENABLE_EXTRAS], [test "${enable_extras}" = "yes"]) AM_CONDITIONAL([ENABLE_QUARANTINED], [test "${enable_quarantined}" = "yes"]) +AM_CONDITIONAL([DISABLE_PLUGINS], [test "${disable_plugins}" != "no"]) # workaround for flags indicates the kind of opening. + * The field fi->fh may be used to store some information which + * will be available to subsequent reads and writes. When used + * this field must be non-null. + * The returned value is zero for success and a negative errno + * value for failure. + */ + int (*open)(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct fuse_file_info *fi); + + /* + * Release an open file + * This is only called if fi->fh has been set to a non-null + * value while opening. It may be used to free some context + * specific to the open file. + * The returned value is zero for success or a negative errno + * value for failure. + */ + int (*release)(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct fuse_file_info *fi); + + /* + * Read from an open file + * The returned value is the count of bytes which were read + * or a negative errno value for failure. + * If the returned value is positive, the access time stamp + * will be updated after the call. + */ + int (*read)(ntfs_inode *ni, const REPARSE_POINT *reparse, + char *buf, size_t size, + off_t offset, struct fuse_file_info *fi); + + /* + * Write to an open file + * The file system must be left consistent after each write call, + * the file itself must be at least deletable if the application + * writing to it is killed for some reason. + * The returned value is the count of bytes which were written + * or a negative errno value for failure. + * If the returned value is positive, the modified time stamp + * will be updated after the call. + */ + int (*write)(ntfs_inode *ni, const REPARSE_POINT *reparse, + const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi); + + /* + * Get a symbolic link + * The symbolic link must be returned in an allocated buffer, + * encoded in a zero terminated multibyte string compatible + * which the locale mount option. + * The returned value is zero for success or a negative errno + * value for failure. + */ + int (*readlink)(ntfs_inode *ni, const REPARSE_POINT *reparse, + char **pbuf); + + /* + * Truncate a file (shorten or append zeroes) + * The returned value is zero for success or a negative errno + * value for failure. + * If the returned value is zero, the modified time stamp + * will be updated after the call. + */ + int (*truncate)(ntfs_inode *ni, const REPARSE_POINT *reparse, + off_t size); +} plugin_operations_t; + + +/* + * Plugin initialization routine + * Returns the entry table if successful, otherwise returns NULL + * and sets errno (e.g. to EINVAL if the tag is not supported by + * the plugin.) + */ +typedef const struct plugin_operations *(*plugin_init_t)(le32 tag); +const struct plugin_operations *init(le32 tag); + +#endif /* PLUGIN_H */ diff --git a/include/ntfs-3g/reparse.h b/include/ntfs-3g/reparse.h index 35f4aa45..27e90509 100644 --- a/include/ntfs-3g/reparse.h +++ b/include/ntfs-3g/reparse.h @@ -30,6 +30,8 @@ BOOL ntfs_possible_symlink(ntfs_inode *ni); int ntfs_get_ntfs_reparse_data(ntfs_inode *ni, char *value, size_t size); +REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni); + int ntfs_set_ntfs_reparse_data(ntfs_inode *ni, const char *value, size_t size, int flags); int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni); diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index fe9d361b..5e2f403e 100644 --- a/libntfs-3g/reparse.c +++ b/libntfs-3g/reparse.c @@ -3,7 +3,7 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2008-2014 Jean-Pierre Andre + * Copyright (c) 2008-2016 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -1256,3 +1256,31 @@ int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni) } #endif /* HAVE_SETXATTR */ + +/* + * Get the reparse data into a buffer + * + * Returns the buffer if the reparse data exists and is valid + * NULL otherwise (with errno set according to the cause). + * When a buffer is returned, it has to be freed by caller. + */ + +REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni) +{ + s64 attr_size = 0; + REPARSE_POINT *reparse_attr; + + reparse_attr = (REPARSE_POINT*)NULL; + if (ni) { + reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni, + AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size); + if (reparse_attr + && !valid_reparse_data(ni, reparse_attr, attr_size)) { + free(reparse_attr); + reparse_attr = (REPARSE_POINT*)NULL; + errno = ENOENT; + } + } else + errno = EINVAL; + return (reparse_attr); +} diff --git a/src/Makefile.am b/src/Makefile.am index 7fd4af45..4edbcbc4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,11 @@ FUSE_CFLAGS = $(FUSE_MODULE_CFLAGS) FUSE_LIBS = $(FUSE_MODULE_LIBS) endif +if !DISABLE_PLUGINS +plugindir = $(libdir)/ntfs-3g +PLUGIN_CFLAGS = -DPLUGIN_DIR=\"$(plugindir)\" +endif + if ENABLE_NTFS_3G bin_PROGRAMS = ntfs-3g.probe \ @@ -22,7 +27,7 @@ man_MANS = ntfs-3g.8 ntfs-3g.probe.8 \ ntfs-3g.usermap.8 \ ntfs-3g.secaudit.8 -ntfs_3g_LDADD = $(FUSE_LIBS) $(top_builddir)/libntfs-3g/libntfs-3g.la +ntfs_3g_LDADD = -ldl $(FUSE_LIBS) $(top_builddir)/libntfs-3g/libntfs-3g.la if REALLYSTATIC ntfs_3g_LDFLAGS = $(AM_LDFLAGS) -all-static endif @@ -30,10 +35,11 @@ ntfs_3g_CFLAGS = \ $(AM_CFLAGS) \ -DFUSE_USE_VERSION=26 \ $(FUSE_CFLAGS) \ - -I$(top_srcdir)/include/ntfs-3g + -I$(top_srcdir)/include/ntfs-3g \ + $(PLUGIN_CFLAGS) ntfs_3g_SOURCES = ntfs-3g.c ntfs-3g_common.c -lowntfs_3g_LDADD = $(FUSE_LIBS) $(top_builddir)/libntfs-3g/libntfs-3g.la +lowntfs_3g_LDADD = -ldl $(FUSE_LIBS) $(top_builddir)/libntfs-3g/libntfs-3g.la if REALLYSTATIC lowntfs_3g_LDFLAGS = $(AM_LDFLAGS) -all-static endif @@ -41,7 +47,8 @@ lowntfs_3g_CFLAGS = \ $(AM_CFLAGS) \ -DFUSE_USE_VERSION=26 \ $(FUSE_CFLAGS) \ - -I$(top_srcdir)/include/ntfs-3g + -I$(top_srcdir)/include/ntfs-3g \ + $(PLUGIN_CFLAGS) lowntfs_3g_SOURCES = lowntfs-3g.c ntfs-3g_common.c ntfs_3g_probe_LDADD = $(top_builddir)/libntfs-3g/libntfs-3g.la @@ -61,10 +68,13 @@ ntfs_3g_secaudit_SOURCES = secaudit.c drivers : $(FUSE_LIBS) ntfs-3g lowntfs-3g -if RUN_LDCONFIG install-exec-hook: +if RUN_LDCONFIG $(LDCONFIG) endif +if !DISABLE_PLUGINS + $(MKDIR_P) $(DESTDIR)/$(plugindir) +endif if ENABLE_MOUNT_HELPER install-exec-local: install-rootbinPROGRAMS diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 0bb38f97..899586c1 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -4,7 +4,7 @@ * 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) 2007-2016 Jean-Pierre Andre * Copyright (c) 2009 Erik Larsson * * This file is originated from the Linux-NTFS project. @@ -105,6 +105,7 @@ #include "xattrs.h" #include "misc.h" #include "ioctl.h" +#include "plugin.h" #include "ntfs-3g_common.h" @@ -157,6 +158,22 @@ #define set_archive(ni) (ni)->flags |= FILE_ATTR_ARCHIVE #define INODE(ino) ((ino) == 1 ? (MFT_REF)FILE_root : (MFT_REF)(ino)) +/* + * Call a function from a reparse plugin (variable arguments) + * Requires "reparse" and "ops" to have been defined + * + * Returns a non-negative value if successful, + * and a negative error code if something fails. + */ +#define CALL_REPARSE_PLUGIN(ni, op_name, ...) \ + (reparse = (REPARSE_POINT*)NULL, \ + ops = select_reparse_plugin(ctx, ni, &reparse), \ + (!ops ? -errno \ + : (ops->op_name ? \ + ops->op_name(ni, reparse, __VA_ARGS__) \ + : -EOPNOTSUPP))), \ + free(reparse) + typedef enum { FSTYPE_NONE, FSTYPE_UNKNOWN, @@ -187,13 +204,17 @@ struct open_file { fuse_ino_t ino; fuse_ino_t parent; int state; +#ifndef PLUGINS_DISABLED + struct fuse_file_info fi; +#endif /* PLUGINS_DISABLED */ } ; enum { CLOSE_GHOST = 1, CLOSE_COMPRESSED = 2, CLOSE_ENCRYPTED = 4, - CLOSE_DMTIME = 8 + CLOSE_DMTIME = 8, + CLOSE_REPARSE = 16 }; enum RM_TYPES { @@ -598,6 +619,67 @@ static void ntfs_init(void *userdata __attribute__((unused)), #endif /* defined(FUSE_CAP_IOCTL_DIR) */ } +#ifndef PLUGINS_DISABLED + +/* + * Define attributes for a junction or symlink + * (internal plugin) + */ + +static int junction_getstat(ntfs_inode *ni, + const REPARSE_POINT *reparse __attribute__((unused)), + struct stat *stbuf) +{ + char *target; + int attr_size; + int res; + + 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_mode = S_IFLNK; + free(target); + res = 0; + } else { + res = -errno; + } + return (res); +} + +/* + * Apply permission masks to st_mode returned by reparse handler + */ + +static void apply_umask(struct stat *stbuf) +{ + switch (stbuf->st_mode & S_IFMT) { + case S_IFREG : + stbuf->st_mode &= ~ctx->fmask; + break; + case S_IFDIR : + stbuf->st_mode &= ~ctx->dmask; + break; + case S_IFLNK : + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | 0777; + break; + default : + break; + } +} + +#endif /* PLUGINS_DISABLED */ + static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, struct stat *stbuf) { @@ -607,9 +689,27 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, memset(stbuf, 0, sizeof(struct stat)); withusermapping = (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL); + stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) || (ni->flags & FILE_ATTR_REPARSE_POINT)) { if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(ni, getattr, stbuf); + if (!res) { + apply_umask(stbuf); + } else { + stbuf->st_size = + sizeof(ntfs_bad_reparse); + stbuf->st_blocks = + (ni->allocated_size + 511) >> 9; + stbuf->st_mode = S_IFLNK; + res = 0; + } + goto ok; +#else /* PLUGINS_DISABLED */ char *target; int attr_size; @@ -638,6 +738,7 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, res = -errno; goto exit; } +#endif /* PLUGINS_DISABLED */ } else { /* Directory. */ stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); @@ -676,7 +777,6 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, * 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) { @@ -742,6 +842,9 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, } stbuf->st_mode |= (0777 & ~ctx->fmask); } +#ifndef PLUGINS_DISABLED +ok: +#endif /* PLUGINS_DISABLED */ if (withusermapping) { if (ntfs_get_owner_mode(scx,ni,stbuf) < 0) set_fuse_error(&res); @@ -884,6 +987,36 @@ static void ntfs_fuse_lookup(fuse_req_t req, fuse_ino_t parent, fuse_reply_entry(req, &entry); } +#ifndef PLUGINS_DISABLED + +/* + * Get the link defined by a junction or symlink + * (internal plugin) + */ + +static int junction_readlink(ntfs_inode *ni, + const REPARSE_POINT *reparse __attribute__((unused)), + char **pbuf) +{ + int attr_size; + int res; + + errno = 0; + res = 0; + *pbuf = ntfs_make_symlink(ni, ctx->abs_mnt_point, &attr_size); + if (!*pbuf) { + if (errno == EOPNOTSUPP) { + *pbuf = strdup(ntfs_bad_reparse); + if (!*pbuf) + res = -errno; + } else + res = -errno; + } + return (res); +} + +#endif /* PLUGINS_DISABLED */ + static void ntfs_fuse_readlink(fuse_req_t req, fuse_ino_t ino) { ntfs_inode *ni = NULL; @@ -902,6 +1035,17 @@ static void ntfs_fuse_readlink(fuse_req_t req, fuse_ino_t ino) * Reparse point : analyze as a junction point */ if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(ni, readlink, &buf); + if (res) { + buf = strdup(ntfs_bad_reparse); + if (!buf) + res = -errno; + } +#else /* PLUGINS_DISABLED */ int attr_size; errno = 0; @@ -913,7 +1057,8 @@ static void ntfs_fuse_readlink(fuse_req_t req, fuse_ino_t ino) if (!buf) res = -errno; } - goto exit; +#endif /* PLUGINS_DISABLED */ + goto exit; } /* Sanity checks. */ if (!(ni->flags & FILE_ATTR_SYSTEM)) { @@ -1266,10 +1411,9 @@ static void ntfs_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { ntfs_inode *ni; - ntfs_attr *na; + ntfs_attr *na = NULL; struct open_file *of; int state = 0; - char *path = NULL; int res = 0; #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) int accesstype; @@ -1278,56 +1422,77 @@ static void ntfs_fuse_open(fuse_req_t req, fuse_ino_t ino, ni = ntfs_inode_open(ctx->vol, INODE(ino)); if (ni) { - na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); - if (na) { + if (!(ni->flags & FILE_ATTR_REPARSE_POINT)) { + na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); + if (!na) { + res = -errno; + goto close; + } + } #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) - if (ntfs_fuse_fill_security_context(req, &security)) { - if (fi->flags & O_WRONLY) - accesstype = S_IWRITE; + 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 - 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; - } + 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; + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + fi->fh = 0; + res = CALL_REPARSE_PLUGIN(ni, open, fi); + if (!res && fi->fh) { + state = CLOSE_REPARSE; } - ntfs_attr_close(na); - } else - res = -errno; +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto close; + } + 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); +close: 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; +#ifdef PLUGIN_ENABLED + memcpy(&of->fi, fi, sizeof(struct fuse_file_info)); +#endif /* PLUGIN_ENABLED */ of->next = ctx->open_files; of->previous = (struct open_file*)NULL; if (ctx->open_files) @@ -1368,6 +1533,22 @@ static void ntfs_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, res = -errno; goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + struct open_file *of; + + of = (struct open_file*)fi; + res = CALL_REPARSE_PLUGIN(ni, read, buf, size, offset, &of->fi); + if (res >= 0) { + goto stamps; + } +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto exit; + } na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); if (!na) { res = -errno; @@ -1403,8 +1584,11 @@ static void ntfs_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, total += ret; } ok: - ntfs_fuse_update_times(na->ni, NTFS_UPDATE_ATIME); res = total; +#ifndef PLUGINS_DISABLED +stamps : +#endif /* PLUGINS_DISABLED */ + ntfs_fuse_update_times(ni, NTFS_UPDATE_ATIME); exit: if (na) ntfs_attr_close(na); @@ -1430,6 +1614,23 @@ static void ntfs_fuse_write(fuse_req_t req, fuse_ino_t ino, const char *buf, res = -errno; goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + struct open_file *of; + + of = (struct open_file*)fi; + res = CALL_REPARSE_PLUGIN(ni, write, buf, size, offset, + &of->fi); + if (res >= 0) { + goto stamps; + } +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto exit; + } na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); if (!na) { res = -errno; @@ -1446,15 +1647,18 @@ static void ntfs_fuse_write(fuse_req_t req, fuse_ino_t ino, const char *buf, total += ret; } res = total; +#ifndef PLUGINS_DISABLED +stamps : +#endif /* PLUGINS_DISABLED */ 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); + ntfs_fuse_update_times(ni, NTFS_UPDATE_MCTIME); exit: if (na) ntfs_attr_close(na); - if (total) + if (res > 0) set_archive(ni); if (ntfs_inode_close(ni)) set_fuse_error(&res); @@ -1607,9 +1811,11 @@ static int ntfs_fuse_trunc(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, errno = EPERM; goto exit; } - na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0); - if (!na) - goto exit; + if (!(ni->flags & FILE_ATTR_REPARSE_POINT)) { + 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 @@ -1622,6 +1828,21 @@ static int ntfs_fuse_trunc(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, goto exit; } #endif + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(ni, truncate, size); + if (!res) { + set_archive(ni); + goto stamps; + } +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto exit; + } /* * for compressed files, upsizing is done by inserting a final * zero, which is optimized as creating a hole when possible. @@ -1637,8 +1858,11 @@ static int ntfs_fuse_trunc(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, goto exit; if (oldsize != size) set_archive(ni); - - ntfs_fuse_update_times(na->ni, NTFS_UPDATE_MCTIME); + +#ifndef PLUGINS_DISABLED +stamps : +#endif /* PLUGINS_DISABLED */ + ntfs_fuse_update_times(ni, NTFS_UPDATE_MCTIME); res = ntfs_fuse_getstat(scx, ni, stbuf); errno = (res ? -res : 0); exit: @@ -2550,8 +2774,8 @@ static void ntfs_fuse_release(fuse_req_t req, fuse_ino_t ino, 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))) { + || !(of->state & (CLOSE_COMPRESSED | CLOSE_ENCRYPTED + | CLOSE_DMTIME | CLOSE_REPARSE))) { res = 0; goto out; } @@ -2560,20 +2784,38 @@ static void ntfs_fuse_release(fuse_req_t req, fuse_ino_t ino, res = -errno; goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(ni, release, &of->fi); + if (!res) { + goto stamps; + } +#else /* PLUGINS_DISABLED */ + /* Assume release() was not needed */ + res = 0; +#endif /* PLUGINS_DISABLED */ + 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 */ +#ifndef PLUGINS_DISABLED +stamps : +#endif /* PLUGINS_DISABLED */ + if (of->state & CLOSE_DMTIME) + ntfs_inode_update_times(ni,NTFS_UPDATE_MCTIME); exit: if (na) ntfs_attr_close(na); @@ -3568,6 +3810,20 @@ out : #endif #endif /* HAVE_SETXATTR */ +#ifndef PLUGINS_DISABLED +static void register_internal_reparse_plugins(void) +{ + static const plugin_operations_t ops = { + .getattr = junction_getstat, + .readlink = junction_readlink, + } ; + register_reparse_plugin(ctx, IO_REPARSE_TAG_MOUNT_POINT, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_SYMLINK, + &ops, (void*)NULL); +} +#endif /* PLUGINS_DISABLED */ + static void ntfs_close(void) { struct SECURITY_CONTEXT security; @@ -4179,6 +4435,10 @@ int main(int argc, char *argv[]) free(ctx->xattrmap_path); #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ +#ifndef PLUGINS_DISABLED + register_internal_reparse_plugins(); +#endif /* PLUGINS_DISABLED */ + se = mount_fuse(parsed_options); if (!se) { err = NTFS_VOLUME_FUSE_ERROR; @@ -4214,6 +4474,9 @@ err_out: #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ err2: ntfs_close(); +#ifndef PLUGINS_DISABLED + close_reparse_plugins(ctx); +#endif /* PLUGINS_DISABLED */ free(ctx); free(parsed_options); free(opts.options); diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index 268b0569..e3a23e58 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -4,7 +4,7 @@ * 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) 2007-2016 Jean-Pierre Andre * Copyright (c) 2009 Erik Larsson * * This file is originated from the Linux-NTFS project. @@ -99,6 +99,7 @@ #include "xattrs.h" #include "misc.h" #include "ioctl.h" +#include "plugin.h" #include "ntfs-3g_common.h" @@ -129,6 +130,22 @@ #define set_archive(ni) (ni)->flags |= FILE_ATTR_ARCHIVE +/* + * Call a function from a reparse plugin (variable arguments) + * Requires "reparse" and "ops" to have been defined + * + * Returns a non-negative value if successful, + * and a negative error code if something fails. + */ +#define CALL_REPARSE_PLUGIN(ni, op_name, ...) \ + (reparse = (REPARSE_POINT*)NULL, \ + ops = select_reparse_plugin(ctx, ni, &reparse), \ + (!ops ? -errno \ + : (ops->op_name ? \ + ops->op_name(ni, reparse, __VA_ARGS__) \ + : -EOPNOTSUPP))), \ + free(reparse) + typedef enum { FSTYPE_NONE, FSTYPE_UNKNOWN, @@ -144,7 +161,8 @@ typedef struct { enum { CLOSE_COMPRESSED = 1, CLOSE_ENCRYPTED = 2, - CLOSE_DMTIME = 4 + CLOSE_DMTIME = 4, + CLOSE_REPARSE = 8 }; static struct ntfs_options opts; @@ -663,6 +681,67 @@ static void *ntfs_init(struct fuse_conn_info *conn) return NULL; } +#ifndef PLUGINS_DISABLED + +/* + * Define attributes for a junction or symlink + * (internal plugin) + */ + +static int junction_getattr(ntfs_inode *ni, + const REPARSE_POINT *reparse __attribute__((unused)), + struct stat *stbuf) +{ + char *target; + int attr_size; + int res; + + 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_mode = S_IFLNK; + free(target); + res = 0; + } else { + res = -errno; + } + return (res); +} + +/* + * Apply permission masks to st_mode returned by a reparse handler + */ + +static void apply_umask(struct stat *stbuf) +{ + switch (stbuf->st_mode & S_IFMT) { + case S_IFREG : + stbuf->st_mode &= ~ctx->fmask; + break; + case S_IFDIR : + stbuf->st_mode &= ~ctx->dmask; + break; + case S_IFLNK : + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | 0777; + break; + default : + break; + } +} + +#endif /* PLUGINS_DISABLED */ + static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) { int res = 0; @@ -696,10 +775,31 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) goto exit; } #endif + stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + if (((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) || (ni->flags & FILE_ATTR_REPARSE_POINT)) && !stream_name_len) { if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(ni, getattr, stbuf); + if (!res) { + apply_umask(stbuf); + goto ok; + } else { + stbuf->st_size = + sizeof(ntfs_bad_reparse); + stbuf->st_blocks = + (ni->allocated_size + 511) >> 9; + stbuf->st_mode = S_IFLNK; + res = 0; + goto ok; + } + goto exit; +#else /* PLUGINS_DISABLED */ char *target; int attr_size; @@ -724,6 +824,7 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) res = -errno; goto exit; } +#endif /* PLUGINS_DISABLED */ } else { /* Directory. */ stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); @@ -761,7 +862,6 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) * 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 || stream_name_len) { na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); @@ -845,6 +945,9 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) } stbuf->st_mode |= (0777 & ~ctx->fmask); } +#ifndef PLUGINS_DISABLED +ok: +#endif /* PLUGINS_DISABLED */ if (withusermapping) { if (ntfs_get_owner_mode(&security,ni,stbuf) < 0) set_fuse_error(&res); @@ -900,6 +1003,36 @@ exit: return res; } +#ifndef PLUGINS_DISABLED + +/* + * Get the link defined by a junction or symlink + * (internal plugin) + */ + +static int junction_readlink(ntfs_inode *ni, + const REPARSE_POINT *reparse __attribute__((unused)), + char **pbuf) +{ + int attr_size; + int res; + + errno = 0; + res = 0; + *pbuf = ntfs_make_symlink(ni, ctx->abs_mnt_point, &attr_size); + if (!*pbuf) { + if (errno == EOPNOTSUPP) { + *pbuf = strdup(ntfs_bad_reparse); + if (!*pbuf) + res = -errno; + } else + res = -errno; + } + return (res); +} + +#endif /* PLUGINS_DISABLED */ + static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) { char *path = NULL; @@ -926,6 +1059,21 @@ static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) * Reparse point : analyze as a junction point */ if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + char *gotlink; + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + gotlink = (char*)NULL; + res = CALL_REPARSE_PLUGIN(ni, readlink, &gotlink); + if (gotlink) { + strncpy(buf, gotlink, buf_size); + free(gotlink); + } else { + strncpy(buf, ntfs_bad_reparse, buf_size); + res = 0; + } +#else /* PLUGINS_DISABLED */ char *target; int attr_size; @@ -940,6 +1088,7 @@ static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) strcpy(buf,ntfs_bad_reparse); else res = -errno; +#endif /* PLUGINS_DISABLED */ goto exit; } /* Sanity checks. */ @@ -1163,7 +1312,7 @@ static int ntfs_fuse_open(const char *org_path, #endif { ntfs_inode *ni; - ntfs_attr *na; + ntfs_attr *na = NULL; int res = 0; char *path = NULL; ntfschar *stream_name; @@ -1178,50 +1327,66 @@ static int ntfs_fuse_open(const char *org_path, return stream_name_len; ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (ni) { - na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); - if (na) { -#if !KERNELPERMS | (POSIXACLS & !KERNELACLS) - if (ntfs_fuse_fill_security_context(&security)) { - if (fi->flags & O_WRONLY) - accesstype = S_IWRITE; - else - if (fi->flags & O_RDWR) - accesstype = S_IWRITE | S_IREAD; - else - accesstype = S_IREAD; - /* - * directory must be searchable - * and requested access allowed - */ - if (!ntfs_allowed_dir_access(&security, - path,(ntfs_inode*)NULL,ni,S_IEXEC) - || !ntfs_allowed_access(&security, - ni,accesstype)) - res = -EACCES; + if (!(ni->flags & FILE_ATTR_REPARSE_POINT)) { + na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); + if (!na) { + res = -errno; + goto close; } + } +#if !KERNELPERMS | (POSIXACLS & !KERNELACLS) + if (ntfs_fuse_fill_security_context(&security)) { + if (fi->flags & O_WRONLY) + accesstype = S_IWRITE; + else + if (fi->flags & O_RDWR) + accesstype = S_IWRITE | S_IREAD; + else + accesstype = S_IREAD; + /* + * directory must be searchable + * and requested access allowed + */ + if (!ntfs_allowed_dir_access(&security, + path,(ntfs_inode*)NULL,ni,S_IEXEC) + || !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) - fi->fh |= CLOSE_COMPRESSED; + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + fi->fh = 0; + res = CALL_REPARSE_PLUGIN(ni, open, fi); +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto close; + } + 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) + fi->fh |= 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)) - fi->fh |= CLOSE_ENCRYPTED; + if (ctx->efs_raw + && !(na->data_flags & ATTR_IS_ENCRYPTED) + && (ni->flags & FILE_ATTR_ENCRYPTED)) + fi->fh |= CLOSE_ENCRYPTED; #endif /* HAVE_SETXATTR */ - /* mark a future need to update the mtime */ - if (ctx->dmtime) - fi->fh |= CLOSE_DMTIME; - /* deny opening metadata files for writing */ - if (ni->mft_no < FILE_first_user) - res = -EPERM; - } - ntfs_attr_close(na); - } else - res = -errno; + /* mark a future need to update the mtime */ + if (ctx->dmtime) + fi->fh |= CLOSE_DMTIME; + /* deny opening metadata files for writing */ + if (ni->mft_no < FILE_first_user) + res = -EPERM; + } + ntfs_attr_close(na); +close: if (ntfs_inode_close(ni)) set_fuse_error(&res); } else @@ -1254,6 +1419,24 @@ static int ntfs_fuse_read(const char *org_path, char *buf, size_t size, res = -errno; goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + if (stream_name_len || !fi) { + res = -EINVAL; + goto exit; + } + res = CALL_REPARSE_PLUGIN(ni, read, buf, size, offset, fi); + if (res >= 0) { + goto stamps; + } +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto exit; + } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; @@ -1289,8 +1472,11 @@ static int ntfs_fuse_read(const char *org_path, char *buf, size_t size, total += ret; } ok: - ntfs_fuse_update_times(na->ni, NTFS_UPDATE_ATIME); res = total; +#ifndef PLUGINS_DISABLED +stamps: +#endif /* PLUGINS_DISABLED */ + ntfs_fuse_update_times(ni, NTFS_UPDATE_ATIME); exit: if (na) ntfs_attr_close(na); @@ -1321,6 +1507,24 @@ static int ntfs_fuse_write(const char *org_path, const char *buf, size_t size, res = -errno; goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + if (stream_name_len || !fi) { + res = -EINVAL; + goto exit; + } + res = CALL_REPARSE_PLUGIN(ni, write, buf, size, offset, fi); + if (res >= 0) { + goto stamps; + } +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto exit; + } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; @@ -1337,15 +1541,18 @@ static int ntfs_fuse_write(const char *org_path, const char *buf, size_t size, total += ret; } res = total; +#ifndef PLUGINS_DISABLED +stamps: +#endif /* PLUGINS_DISABLED */ 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); + ntfs_fuse_update_times(ni, NTFS_UPDATE_MCTIME); exit: if (na) ntfs_attr_close(na); - if (total) + if (res > 0) set_archive(ni); if (ntfs_inode_close(ni)) set_fuse_error(&res); @@ -1365,8 +1572,14 @@ static int ntfs_fuse_release(const char *org_path, ntfschar *stream_name; int stream_name_len, res; + if (!fi) { + res = -EINVAL; + goto out; + } + /* Only for marked descriptors there is something to do */ - if (!(fi->fh & (CLOSE_COMPRESSED | CLOSE_ENCRYPTED | CLOSE_DMTIME))) { + + if (!fi->fh) { res = 0; goto out; } @@ -1380,20 +1593,42 @@ static int ntfs_fuse_release(const char *org_path, res = -errno; goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + if (stream_name_len) { + res = -EINVAL; + goto exit; + } + res = CALL_REPARSE_PLUGIN(ni, release, fi); + if (!res) { + goto stamps; + } +#else /* PLUGINS_DISABLED */ + /* Assume release() was not needed */ + res = 0; +#endif /* PLUGINS_DISABLED */ + goto exit; + } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) { res = -errno; goto exit; } res = 0; - if (fi->fh & CLOSE_DMTIME) - ntfs_inode_update_times(na->ni,NTFS_UPDATE_MCTIME); if (fi->fh & CLOSE_COMPRESSED) res = ntfs_attr_pclose(na); #ifdef HAVE_SETXATTR /* extended attributes interface required */ if (fi->fh & CLOSE_ENCRYPTED) res = ntfs_efs_fixup_attribute(NULL, na); #endif /* HAVE_SETXATTR */ +#ifndef PLUGINS_DISABLED +stamps: +#endif /* PLUGINS_DISABLED */ + if (fi->fh & CLOSE_DMTIME) + ntfs_inode_update_times(ni,NTFS_UPDATE_MCTIME); exit: if (na) ntfs_attr_close(na); @@ -1440,6 +1675,25 @@ static int ntfs_fuse_trunc(const char *org_path, off_t size, goto exit; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef PLUGINS_DISABLED + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + if (stream_name_len) { + res = -EINVAL; + goto exit; + } + res = CALL_REPARSE_PLUGIN(ni, truncate, size); + if (!res) { + set_archive(ni); + goto stamps; + } +#else /* PLUGINS_DISABLED */ + res = -EOPNOTSUPP; +#endif /* PLUGINS_DISABLED */ + goto exit; + } na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_len); if (!na) goto exit; @@ -1472,8 +1726,11 @@ static int ntfs_fuse_trunc(const char *org_path, off_t size, goto exit; if (oldsize != size) set_archive(ni); - - ntfs_fuse_update_times(na->ni, NTFS_UPDATE_MCTIME); + +#ifndef PLUGINS_DISABLED +stamps: +#endif /* PLUGINS_DISABLED */ + ntfs_fuse_update_times(ni, NTFS_UPDATE_MCTIME); errno = 0; exit: res = -errno; @@ -3352,6 +3609,20 @@ exit: #endif #endif /* HAVE_SETXATTR */ +#ifndef PLUGINS_DISABLED +static void register_internal_reparse_plugins(void) +{ + static const plugin_operations_t ops = { + .getattr = junction_getattr, + .readlink = junction_readlink, + } ; + register_reparse_plugin(ctx, IO_REPARSE_TAG_MOUNT_POINT, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_SYMLINK, + &ops, (void*)NULL); +} +#endif /* PLUGINS_DISABLED */ + static void ntfs_close(void) { struct SECURITY_CONTEXT security; @@ -3968,6 +4239,10 @@ int main(int argc, char *argv[]) free(ctx->xattrmap_path); #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ +#ifndef PLUGINS_DISABLED + register_internal_reparse_plugins(); +#endif /* PLUGINS_DISABLED */ + fh = mount_fuse(parsed_options); if (!fh) { err = NTFS_VOLUME_FUSE_ERROR; @@ -4005,6 +4280,9 @@ err_out: #endif /* defined(HAVE_SETXATTR) && defined(XATTR_MAPPINGS) */ err2: ntfs_close(); +#ifndef PLUGINS_DISABLED + close_reparse_plugins(ctx); +#endif /* PLUGINS_DISABLED */ free(ctx); free(parsed_options); free(opts.options); diff --git a/src/ntfs-3g_common.c b/src/ntfs-3g_common.c index 38ccd742..425e8102 100644 --- a/src/ntfs-3g_common.c +++ b/src/ntfs-3g_common.c @@ -1,7 +1,7 @@ /** * ntfs-3g_common.c - Common definitions for ntfs-3g and lowntfs-3g. * - * Copyright (c) 2010-2015 Jean-Pierre Andre + * Copyright (c) 2010-2016 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -28,6 +28,10 @@ #include #endif +#ifdef HAVE_DLFCN_H +#include +#endif + #ifdef HAVE_STRING_H #include #endif @@ -46,6 +50,8 @@ #include "inode.h" #include "security.h" #include "xattrs.h" +#include "reparse.h" +#include "plugin.h" #include "ntfs-3g_common.h" #include "realpath.h" #include "misc.h" @@ -750,3 +756,109 @@ exit : } #endif /* HAVE_SETXATTR */ + +#ifndef PLUGINS_DISABLED + +int register_reparse_plugin(ntfs_fuse_context_t *ctx, le32 tag, + const plugin_operations_t *ops, void *handle) +{ + plugin_list_t *plugin; + int res; + + res = -1; + plugin = (plugin_list_t*)ntfs_malloc(sizeof(plugin_list_t)); + if (plugin) { + plugin->tag = tag; + plugin->ops = ops; + plugin->handle = handle; + plugin->next = ctx->plugins; + ctx->plugins = plugin; + res = 0; + } + return (res); +} + +/* + * Get the reparse operations associated to an inode + * + * The plugin able to process the reparse point is dynamically loaded + * + * When successful, returns the operations vector and the reparse + * data if requested, + * Otherwise returns NULL, with errno set. + */ + +const struct plugin_operations *select_reparse_plugin(ntfs_fuse_context_t *ctx, + ntfs_inode *ni, REPARSE_POINT **reparse_wanted) +{ + const struct plugin_operations *ops; + void *handle; + REPARSE_POINT *reparse; + le32 tag; + plugin_list_t *plugin; + plugin_init_t pinit; + + ops = (struct plugin_operations*)NULL; + reparse = ntfs_get_reparse_point(ni); + if (reparse) { + tag = reparse->reparse_tag; + for (plugin=ctx->plugins; plugin && (plugin->tag != tag); + plugin = plugin->next) { } + if (plugin) { + ops = plugin->ops; + } else { +#ifdef PLUGIN_DIR + char name[sizeof(PLUGIN_DIR) + 64]; + + snprintf(name,sizeof(name), PLUGIN_DIR + "/ntfs-plugin-%08lx.so", + (long)le32_to_cpu(tag)); +#else + char name[64]; + + snprintf(name,sizeof(name), "ntfs-plugin-%08lx.so", + (long)le32_to_cpu(tag)); +#endif + handle = dlopen(name, RTLD_LAZY); + if (handle) { + pinit = (plugin_init_t)dlsym(handle, "init"); + if (pinit) { + /* pinit() should set errno if it fails */ + ops = (*pinit)(tag); + if (ops && register_reparse_plugin(ctx, + tag, ops, handle)) + ops = (struct plugin_operations*)NULL; + } else + errno = ELIBBAD; + if (!ops) + dlclose(handle); + } else { + if (!(ctx->errors_logged & ERR_PLUGIN)) + ntfs_log_perror( + "Could not load plugin %s", + name); + ctx->errors_logged |= ERR_PLUGIN; + } + } + if (ops && reparse_wanted) + *reparse_wanted = reparse; + else + free(reparse); + } + return (ops); +} + +void close_reparse_plugins(ntfs_fuse_context_t *ctx) +{ + while (ctx->plugins) { + plugin_list_t *next; + + next = ctx->plugins->next; + if (ctx->plugins->handle) + dlclose(ctx->plugins->handle); + free(ctx->plugins); + ctx->plugins = next; + } +} + +#endif /* PLUGINS_DISABLED */ diff --git a/src/ntfs-3g_common.h b/src/ntfs-3g_common.h index 6e573a65..22452321 100644 --- a/src/ntfs-3g_common.h +++ b/src/ntfs-3g_common.h @@ -110,6 +110,21 @@ typedef enum { ATIME_RELATIVE } ntfs_atime_t; +typedef enum { + ERR_PLUGIN = 1 +} single_log_t; + +#ifndef PLUGINS_DISABLED + +typedef struct plugin_list { + struct plugin_list *next; + void *handle; + const plugin_operations_t *ops; + le32 tag; +} plugin_list_t; + +#endif /* PLUGINS_DISABLED */ + typedef struct { ntfs_volume *vol; unsigned int uid; @@ -145,8 +160,12 @@ typedef struct { struct fuse_chan *fc; BOOL inherit; unsigned int secure_flags; + single_log_t errors_logged; char *usermap_path; char *abs_mnt_point; +#ifndef PLUGINS_DISABLED + plugin_list_t *plugins; +#endif /* PLUGINS_DISABLED */ struct PERMISSIONS_CACHE *seccache; struct SECURITY_CONTEXT security; struct open_file *open_files; /* only defined in lowntfs-3g */ @@ -182,4 +201,14 @@ int ntfs_parse_options(struct ntfs_options *popts, void (*usage)(void), int ntfs_fuse_listxattr_common(ntfs_inode *ni, ntfs_attr_search_ctx *actx, char *list, size_t size, BOOL prefixing); +#ifndef PLUGINS_DISABLED + +void close_reparse_plugins(ntfs_fuse_context_t *ctx); +const struct plugin_operations *select_reparse_plugin(ntfs_fuse_context_t *ctx, + ntfs_inode *ni, REPARSE_POINT **reparse); +int register_reparse_plugin(ntfs_fuse_context_t *ctx, le32 tag, + const plugin_operations_t *ops, void *handle); + +#endif /* PLUGINS_DISABLED */ + #endif /* _NTFS_3G_COMMON_H */