diff --git a/configure.ac b/configure.ac index 88733eb5..7d8324f5 100644 --- a/configure.ac +++ b/configure.ac @@ -33,7 +33,7 @@ AC_CANONICAL_HOST AC_CANONICAL_TARGET # Automake -AM_INIT_AUTOMAKE([${PACKAGE_NAME}], [${PACKAGE_VERSION}]) +AM_INIT_AUTOMAKE([]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) AM_MAINTAINER_MODE @@ -226,7 +226,9 @@ case "${target}" in ;; esac -if test "x${enable_ntfs_3g}" = "xyes"; then +if test "x${enable_ntfs_3g}" != "xyes"; then + with_fuse="none" +elif test "x${with_fuse}" == "x"; then AC_MSG_CHECKING([fuse compatibility]) case "${target_os}" in linux*|solaris*) @@ -248,8 +250,6 @@ if test "x${enable_ntfs_3g}" = "xyes"; then ;; esac AC_MSG_RESULT([${with_fuse}]) -else - with_fuse="none" fi case "${target_os}" in diff --git a/include/fuse-lite/fuse.h b/include/fuse-lite/fuse.h index 2e3d53d8..b4ec2323 100644 --- a/include/fuse-lite/fuse.h +++ b/include/fuse-lite/fuse.h @@ -432,6 +432,9 @@ struct fuse_operations { * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes. * * Introduced in version 2.8 + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits, and forwarded as a signed int. */ int (*ioctl) (const char *, int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data); diff --git a/include/fuse-lite/fuse_lowlevel.h b/include/fuse-lite/fuse_lowlevel.h index 8c84d965..3b10c35d 100644 --- a/include/fuse-lite/fuse_lowlevel.h +++ b/include/fuse-lite/fuse_lowlevel.h @@ -814,6 +814,9 @@ struct fuse_lowlevel_ops { * * Introduced in version 2.8 * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits, and forwarded as a signed int. + * * Valid replies: * fuse_reply_ioctl_retry * fuse_reply_ioctl diff --git a/include/ntfs-3g/compat.h b/include/ntfs-3g/compat.h index ded6c5f8..288a86a2 100644 --- a/include/ntfs-3g/compat.h +++ b/include/ntfs-3g/compat.h @@ -1,9 +1,10 @@ /* - * compat.h - Tweaks for Windows compatibility. + * compat.h - Tweaks for compatibility with non-Linux systems. * * Copyright (c) 2002 Richard Russon * Copyright (c) 2002-2004 Anton Altaparmakov * Copyright (c) 2008-2009 Szabolcs Szakacsits + * Copyright (c) 2019 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 @@ -41,6 +42,20 @@ #define ELIBBAD ENOEXEC #endif +#ifndef ELIBACC +#define ELIBACC ENOENT +#endif + +/* xattr APIs in macOS differs from Linux ones in that they expect the special + * error code ENOATTR to be returned when an attribute cannot be found. So + * define NTFS_NOXATTR_ERRNO to the appropriate "no xattr found" errno value for + * the platform. */ +#if defined(__APPLE__) || defined(__DARWIN__) +#define NTFS_NOXATTR_ERRNO ENOATTR +#else +#define NTFS_NOXATTR_ERRNO ENODATA +#endif + #ifndef PATH_MAX #define PATH_MAX 4096 #endif diff --git a/include/ntfs-3g/device.h b/include/ntfs-3g/device.h index c7cc9b64..709cb4fa 100644 --- a/include/ntfs-3g/device.h +++ b/include/ntfs-3g/device.h @@ -111,7 +111,8 @@ struct ntfs_device_operations { s64 offset); int (*sync)(struct ntfs_device *dev); int (*stat)(struct ntfs_device *dev, struct stat *buf); - int (*ioctl)(struct ntfs_device *dev, int request, void *argp); + int (*ioctl)(struct ntfs_device *dev, unsigned long request, + void *argp); }; extern struct ntfs_device *ntfs_device_alloc(const char *name, const long state, diff --git a/include/ntfs-3g/dir.h b/include/ntfs-3g/dir.h index e6a2c3b1..60169151 100644 --- a/include/ntfs-3g/dir.h +++ b/include/ntfs-3g/dir.h @@ -94,6 +94,7 @@ extern int ntfs_link(ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name, #define NTFS_DT_LNK 10 #define NTFS_DT_SOCK 12 #define NTFS_DT_WHT 14 +#define NTFS_DT_REPARSE 32 /* * This is the "ntfs_filldir" function type, used by ntfs_readdir() to let @@ -116,6 +117,7 @@ int ntfs_get_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni, int ntfs_set_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni, const char *value, size_t size, int flags); int ntfs_remove_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni); +int ntfs_dir_link_cnt(ntfs_inode *ni); #if CACHE_INODE_SIZE diff --git a/include/ntfs-3g/ea.h b/include/ntfs-3g/ea.h index 1936aab4..cd61fa2f 100644 --- a/include/ntfs-3g/ea.h +++ b/include/ntfs-3g/ea.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-2021 Jean-Pierre Andre * */ @@ -24,6 +24,10 @@ #ifndef EA_H #define EA_H +int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp); + +int ntfs_ea_set_wsl_not_symlink(ntfs_inode *ni, mode_t mode, dev_t dev); + int ntfs_get_ntfs_ea(ntfs_inode *ni, char *value, size_t size); int ntfs_set_ntfs_ea(ntfs_inode *ni, const char *value, size_t size, int flags); diff --git a/include/ntfs-3g/ioctl.h b/include/ntfs-3g/ioctl.h index 4ed6c010..2b25c782 100644 --- a/include/ntfs-3g/ioctl.h +++ b/include/ntfs-3g/ioctl.h @@ -24,7 +24,12 @@ #ifndef IOCTL_H #define IOCTL_H -int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg, +/* + * Using an "unsigned long cmd" internally, like in for Linux + * Note however that fuse truncates the arg to 32 bits, and that + * some commands (e.g. FITRIM) do not fit in a signed 32 bit field. + */ +int ntfs_ioctl(ntfs_inode *ni, unsigned long cmd, void *arg, unsigned int flags, void *data); #endif /* IOCTL_H */ diff --git a/include/ntfs-3g/layout.h b/include/ntfs-3g/layout.h index 318d8dce..5d8e7481 100644 --- a/include/ntfs-3g/layout.h +++ b/include/ntfs-3g/layout.h @@ -916,8 +916,10 @@ typedef enum { FILE_ATTR_OFFLINE = const_cpu_to_le32(0x00001000), FILE_ATTR_NOT_CONTENT_INDEXED = const_cpu_to_le32(0x00002000), FILE_ATTR_ENCRYPTED = const_cpu_to_le32(0x00004000), + /* Supposed to mean no data locally, possibly repurposed */ + FILE_ATTRIBUTE_RECALL_ON_OPEN = const_cpu_to_le32(0x00040000), - FILE_ATTR_VALID_FLAGS = const_cpu_to_le32(0x00007fb7), + FILE_ATTR_VALID_FLAGS = const_cpu_to_le32(0x00047fb7), /* FILE_ATTR_VALID_FLAGS masks out the old DOS VolId and the FILE_ATTR_DEVICE and preserves everything else. This mask is used to obtain all flags that are valid for reading. */ @@ -1503,6 +1505,26 @@ typedef enum { /* This one is for WinNT&2k. */ ACCESS_MAX_MS_ACE_TYPE = 8, + + /* Windows XP and later */ + ACCESS_ALLOWED_CALLBACK_ACE_TYPE = 9, + ACCESS_DENIED_CALLBACK_ACE_TYPE = 10, + ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 11, + ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = 12, + SYSTEM_AUDIT_CALLBACK_ACE_TYPE = 13, + SYSTEM_ALARM_CALLBACK_ACE_TYPE = 14, /* reserved */ + SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = 15, + SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = 16, /* reserved */ + + /* Windows Vista and later */ + SYSTEM_MANDATORY_LABEL_ACE_TYPE = 17, + + /* Windows 8 and later */ + SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE = 18, + SYSTEM_SCOPED_POLICY_ID_ACE_TYPE = 19, + + /* Windows 10 and later */ + SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE = 20, } __attribute__((__packed__)) ACE_TYPES; /** @@ -2523,15 +2545,20 @@ typedef struct { * * 1. The least significant 16 bits (i.e. bits 0 to 15) specify the type of * the reparse point. - * 2. The 13 bits after this (i.e. bits 16 to 28) are reserved for future use. - * 3. The most significant three bits are flags describing the reparse point. + * 2. The 12 bits after this (i.e. bits 16 to 27) are reserved for future use. + * 3. The most significant four bits are flags describing the reparse point. * They are defined as follows: + * bit 28: Directory bit. If set, the directory is not a surrogate + * and can be used the usual way. * bit 29: Name surrogate bit. If set, the filename is an alias for * another object in the system. * bit 30: High-latency bit. If set, accessing the first byte of data will * be slow. (E.g. the data is stored on a tape drive.) * bit 31: Microsoft bit. If set, the tag is owned by Microsoft. User * defined tags have to use zero here. + * 4. Moreover, on Windows 10 : + * Some flags may be used in bits 12 to 15 to further describe the + * reparse point. */ #if ENABLE_STRICT_ENDIANNESS_CHECKING typedef le32 PREDEFINED_REPARSE_TAGS; @@ -2540,6 +2567,7 @@ static const PREDEFINED_REPARSE_TAGS #else typedef enum { #endif + IO_REPARSE_TAG_DIRECTORY = const_cpu_to_le32(0x10000000), IO_REPARSE_TAG_IS_ALIAS = const_cpu_to_le32(0x20000000), IO_REPARSE_TAG_IS_HIGH_LATENCY = const_cpu_to_le32(0x40000000), IO_REPARSE_TAG_IS_MICROSOFT = const_cpu_to_le32(0x80000000), @@ -2559,9 +2587,20 @@ typedef enum { IO_REPARSE_TAG_SIS = const_cpu_to_le32(0x80000007), IO_REPARSE_TAG_SYMLINK = const_cpu_to_le32(0xA000000C), IO_REPARSE_TAG_WIM = const_cpu_to_le32(0x80000008), + IO_REPARSE_TAG_DFM = const_cpu_to_le32(0x80000016), IO_REPARSE_TAG_WOF = const_cpu_to_le32(0x80000017), + IO_REPARSE_TAG_WCI = const_cpu_to_le32(0x80000018), + IO_REPARSE_TAG_CLOUD = const_cpu_to_le32(0x9000001A), + IO_REPARSE_TAG_APPEXECLINK = const_cpu_to_le32(0x8000001B), + IO_REPARSE_TAG_GVFS = const_cpu_to_le32(0x9000001C), + IO_REPARSE_TAG_LX_SYMLINK = const_cpu_to_le32(0xA000001D), + IO_REPARSE_TAG_AF_UNIX = const_cpu_to_le32(0x80000023), + IO_REPARSE_TAG_LX_FIFO = const_cpu_to_le32(0x80000024), + IO_REPARSE_TAG_LX_CHR = const_cpu_to_le32(0x80000025), + IO_REPARSE_TAG_LX_BLK = const_cpu_to_le32(0x80000026), IO_REPARSE_TAG_VALID_VALUES = const_cpu_to_le32(0xf000ffff), + IO_REPARSE_PLUGIN_SELECT = const_cpu_to_le32(0xffff0fff), #if !ENABLE_STRICT_ENDIANNESS_CHECKING } PREDEFINED_REPARSE_TAGS; #else diff --git a/include/ntfs-3g/misc.h b/include/ntfs-3g/misc.h index a03e964e..c5568b39 100644 --- a/include/ntfs-3g/misc.h +++ b/include/ntfs-3g/misc.h @@ -25,6 +25,8 @@ void *ntfs_calloc(size_t size); void *ntfs_malloc(size_t size); +void *ntfs_realloc(void *ptr, size_t size); +void ntfs_free(void *ptr); #endif /* _NTFS_MISC_H_ */ diff --git a/include/ntfs-3g/param.h b/include/ntfs-3g/param.h index 23a728cc..ebc8a0ae 100644 --- a/include/ntfs-3g/param.h +++ b/include/ntfs-3g/param.h @@ -39,6 +39,13 @@ enum { DEFSECBASE = 10000 }; +/* + * Parameters for formatting + */ + + /* Up to Windows 10, the cluster size was limited to 64K */ +#define NTFS_MAX_CLUSTER_SIZE 2097152 /* Windows 10 Creators allows 2MB */ + /* * Parameters for compression */ @@ -112,6 +119,9 @@ enum { * having access control in the file system leads to fewer requests * to the file system and fewer context switches. * + * Irrespective of the selected mode, cacheing is always used + * in read-only mounts + * * Possible values for high level : * 1 : no cache, kernel control (recommended) * 4 : no cache, file system control @@ -120,7 +130,7 @@ enum { * * Possible values for low level : * 2 : no cache, kernel control - * 3 : use kernel/fuse cache, kernel control (recommended) + * 3 : use kernel/fuse cache, kernel control (recommended) * 5 : no cache, file system control * 6 : kernel/fuse cache, file system control (OpenIndiana only) * 8 : no cache, kernel control for ACLs @@ -131,6 +141,7 @@ enum { * of 6 is added in the mount report. */ +#define TIMEOUT_RO 600 /* Attribute time out for read-only mounts */ #if defined(__sun) && defined(__SVR4) /* * Access control by kernel is not implemented on OpenIndiana, @@ -143,9 +154,10 @@ enum { * Cacheing by kernel is buggy on Linux when access control is done * by the file system, and also when using hard-linked files on * the fuse high level interface. + * Also ACL checks by recent kernels do not prove satisfactory. */ #define HPERMSCONFIG 1 -#define LPERMSCONFIG 3 /* Use 9 when ACLs are supported by fuse kernel */ +#define LPERMSCONFIG 3 #endif /* defined(__sun) && defined(__SVR4) */ #endif /* defined _NTFS_PARAM_H */ diff --git a/include/ntfs-3g/plugin.h b/include/ntfs-3g/plugin.h index d32e149d..88e733c4 100644 --- a/include/ntfs-3g/plugin.h +++ b/include/ntfs-3g/plugin.h @@ -1,7 +1,7 @@ /* * plugin.h : define interface for plugin development * - * Copyright (c) 2015 Jean-Pierre Andre + * Copyright (c) 2015-2021 Jean-Pierre Andre * */ @@ -30,8 +30,9 @@ #ifndef _NTFS_PLUGIN_H #define _NTFS_PLUGIN_H -#include "inode.h" #include "layout.h" +#include "inode.h" +#include "dir.h" struct fuse_file_info; struct stat; @@ -71,10 +72,10 @@ typedef struct plugin_operations { struct fuse_file_info *fi); /* - * Release an open file + * Release an open file or directory * 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. + * specific to the open file or directory * The returned value is zero for success or a negative errno * value for failure. */ @@ -110,7 +111,7 @@ typedef struct plugin_operations { * 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. + * with the locale mount option. * The returned value is zero for success or a negative errno * value for failure. */ @@ -126,6 +127,58 @@ typedef struct plugin_operations { */ int (*truncate)(ntfs_inode *ni, const REPARSE_POINT *reparse, off_t size); + /* + * Open a directory + * The field fi->flags indicates the kind of opening. + * The field fi->fh may be used to store some information which + * will be available to subsequent readdir(). When used + * this field must be non-null and freed in release(). + * The returned value is zero for success and a negative errno + * value for failure. + */ + int (*opendir)(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct fuse_file_info *fi); + + /* + * Get entries from a directory + * + * Use the filldir() function with fillctx argument to return + * the directory entries. + * Names "." and ".." are expected to be returned. + * The returned value is zero for success and a negative errno + * value for failure. + */ + int (*readdir)(ntfs_inode *ni, const REPARSE_POINT *reparse, + s64 *pos, void *fillctx, ntfs_filldir_t filldir, + struct fuse_file_info *fi); + /* + * Create a new file of any type + * + * The returned value is a pointer to the inode created, or + * NULL if failed, with errno telling why. + */ + ntfs_inode *(*create)(ntfs_inode *dir_ni, const REPARSE_POINT *reparse, + le32 securid, ntfschar *name, int name_len, + mode_t type); + /* + * Link a new name to a file or directory + * Linking a directory is needed for renaming a directory + * 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 (*link)(ntfs_inode *dir_ni, const REPARSE_POINT *reparse, + ntfs_inode *ni, ntfschar *name, int name_len); + /* + * Unlink a name from a directory + * The argument pathname may be NULL + * The returned value is zero for success or a negative errno + * value for failure. + */ + int (*unlink)(ntfs_inode *dir_ni, const REPARSE_POINT *reparse, + const char *pathname, + ntfs_inode *ni, ntfschar *name, int name_len); } plugin_operations_t; diff --git a/include/ntfs-3g/reparse.h b/include/ntfs-3g/reparse.h index 76af9150..c617951e 100644 --- a/include/ntfs-3g/reparse.h +++ b/include/ntfs-3g/reparse.h @@ -30,8 +30,18 @@ BOOL ntfs_possible_symlink(ntfs_inode *ni); int ntfs_get_ntfs_reparse_data(ntfs_inode *ni, char *value, size_t size); +char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, + int count, const char *mnt_point, BOOL isdir); + REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni); +int ntfs_reparse_check_wsl(ntfs_inode *ni, const REPARSE_POINT *reparse); + +int ntfs_reparse_set_wsl_symlink(ntfs_inode *ni, + const ntfschar *target, int target_len); + +int ntfs_reparse_set_wsl_not_symlink(ntfs_inode *ni, mode_t mode); + 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/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h index cee91e21..30e07906 100644 --- a/include/ntfs-3g/volume.h +++ b/include/ntfs-3g/volume.h @@ -98,6 +98,11 @@ typedef enum { NTFS_VOLUME_INSECURE = 22 } ntfs_volume_status; +typedef enum { + NTFS_FILES_INTERIX, + NTFS_FILES_WSL, +} ntfs_volume_special_files; + /** * enum ntfs_volume_state_bits - * @@ -256,6 +261,8 @@ struct _ntfs_volume { s64 free_mft_records; /* Same for free mft records (see above) */ BOOL efs_raw; /* volume is mounted for raw access to efs-encrypted files */ + ntfs_volume_special_files special_files; /* Implementation of special files */ + const char *abs_mnt_point; /* Mount point */ #ifdef XATTR_MAPPINGS struct XATTRMAPPING *xattr_mapping; #endif /* XATTR_MAPPINGS */ @@ -274,7 +281,6 @@ struct _ntfs_volume { #if CACHE_LEGACY_SIZE struct CACHE_HEADER *legacy_cache; #endif - }; extern const char *ntfs_home; diff --git a/libfuse-lite/Makefile.am b/libfuse-lite/Makefile.am index d9591ec4..ba2934cf 100644 --- a/libfuse-lite/Makefile.am +++ b/libfuse-lite/Makefile.am @@ -7,7 +7,10 @@ endif libfuse_lite_la_CFLAGS= \ $(AM_CFLAGS) \ - $(LIBFUSE_LITE_CFLAGS) \ + $(LIBFUSE_LITE_CFLAGS) + +libfuse_lite_la_CPPFLAGS= \ + $(AM_CPPFLAGS) \ -I$(top_srcdir)/include/fuse-lite libfuse_lite_la_LIBADD = $(LIBFUSE_LITE_LIBS) diff --git a/libfuse-lite/fuse.c b/libfuse-lite/fuse.c index 903a05f7..6f9242b7 100644 --- a/libfuse-lite/fuse.c +++ b/libfuse-lite/fuse.c @@ -134,7 +134,6 @@ struct fuse_dh { struct fuse *fuse; fuse_req_t req; char *contents; - int allocated; unsigned len; unsigned size; unsigned needlen; @@ -464,7 +463,7 @@ static char *add_name(char *buf, char *s, const char *name) return NULL; } #endif /* __SOLARIS__ */ - strncpy(s, name, len); + memcpy(s, name, len); s--; *s = '/'; @@ -2782,8 +2781,10 @@ static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, fuse_finish_interrupt(f, req, &d); free(path); - fuse_reply_ioctl(req, err, out_buf, out_bufsz); + if (err >= 0) { /* not an error */ + fuse_reply_ioctl(req, err, out_buf, out_bufsz); goto out; + } err: reply_err(req, err); out: diff --git a/libfuse-lite/fuse_lowlevel.c b/libfuse-lite/fuse_lowlevel.c index fb93b074..a2a58278 100644 --- a/libfuse-lite/fuse_lowlevel.c +++ b/libfuse-lite/fuse_lowlevel.c @@ -247,7 +247,7 @@ char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf, dirent->off = off; dirent->namelen = namelen; dirent->type = (stbuf->st_mode & 0170000) >> 12; - strncpy(dirent->name, name, namelen); + memcpy(dirent->name, name, namelen); if (padlen) memset(buf + entlen, 0, padlen); diff --git a/libntfs-3g/Makefile.am b/libntfs-3g/Makefile.am index d6b150e5..6feba7d7 100644 --- a/libntfs-3g/Makefile.am +++ b/libntfs-3g/Makefile.am @@ -9,8 +9,8 @@ else noinst_LTLIBRARIES = libntfs-3g.la endif -libntfs_3g_la_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/include/ntfs-3g -libntfs_3g_la_CPPFLAGS= $(AM_CPPFLAGS) $(LIBNTFS_CPPFLAGS) +libntfs_3g_la_CFLAGS = $(AM_CFLAGS) +libntfs_3g_la_CPPFLAGS= $(AM_CPPFLAGS) $(LIBNTFS_CPPFLAGS) -I$(top_srcdir)/include/ntfs-3g libntfs_3g_la_LIBADD = $(LIBNTFS_LIBS) libntfs_3g_la_LDFLAGS = -version-info $(LIBNTFS_3G_VERSION) -no-undefined diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c index 3a20221e..e05924ea 100644 --- a/libntfs-3g/acls.c +++ b/libntfs-3g/acls.c @@ -4,7 +4,7 @@ * This module is part of ntfs-3g library, but may also be * integrated in tools running over Linux or Windows * - * Copyright (c) 2007-2016 Jean-Pierre Andre + * Copyright (c) 2007-2017 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 @@ -560,16 +560,34 @@ static BOOL valid_acl(const ACL *pacl, unsigned int end) pace = (const ACCESS_ALLOWED_ACE*) &((const char*)pacl)[offace]; acesz = le16_to_cpu(pace->size); - if (((offace + acesz) > end) - || !ntfs_valid_sid(&pace->sid)) - ok = FALSE; - else { - /* Win10 may insert garbage in the last ACE */ + switch (pace->type) { + case ACCESS_ALLOWED_ACE_TYPE : + case ACCESS_DENIED_ACE_TYPE : wantsz = ntfs_sid_size(&pace->sid) + 8; - if (((nace < (acecnt - 1)) - && (wantsz != acesz)) + if (((offace + acesz) > end) + || !ntfs_valid_sid(&pace->sid) + || (wantsz != acesz)) + ok = FALSE; + break; + case SYSTEM_AUDIT_ACE_TYPE : + case ACCESS_ALLOWED_CALLBACK_ACE_TYPE : + case ACCESS_DENIED_CALLBACK_ACE_TYPE : + case SYSTEM_AUDIT_CALLBACK_ACE_TYPE : + case SYSTEM_MANDATORY_LABEL_ACE_TYPE : + case SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE : + case SYSTEM_SCOPED_POLICY_ID_ACE_TYPE : + /* Extra data after the SID */ + wantsz = ntfs_sid_size(&pace->sid) + 8; + if (((offace + acesz) > end) + || !ntfs_valid_sid(&pace->sid) || (wantsz > acesz)) ok = FALSE; + break; + default : + /* SID at a different location */ + if ((offace + acesz) > end) + ok = FALSE; + break; } offace += acesz; } @@ -683,6 +701,7 @@ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl, int acesz; int usidsz; int gsidsz; + BOOL acceptable; const ACCESS_ALLOWED_ACE *poldace; ACCESS_ALLOWED_ACE *pnewace; ACCESS_ALLOWED_ACE *pauthace; @@ -707,6 +726,20 @@ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl, poldace = (const ACCESS_ALLOWED_ACE*)((const char*)oldacl + src); acesz = le16_to_cpu(poldace->size); src += acesz; + /* + * Currently only ACE for file or directory access are + * processed. More information needed about what to do + * for other types (whose SID may be at a different location) + */ + switch (poldace->type) { + case ACCESS_ALLOWED_ACE_TYPE : + case ACCESS_DENIED_ACE_TYPE : + acceptable = TRUE; + break; + default : + acceptable = FALSE; + break; + } /* * Extract inheritance for access, including inheritance for * access from an ACE with is both applied and inheritable. @@ -721,6 +754,7 @@ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl, * "information." */ if ((poldace->flags & selection) + && acceptable && (!fordir || (poldace->flags & NO_PROPAGATE_INHERIT_ACE) || !le32_andz(poldace->mask, le32_or(GENERIC_ALL, le32_or(GENERIC_READ, @@ -810,9 +844,10 @@ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl, * Inheritance for access, specific to * creator-owner (and creator-group) */ - if (fordir || le16_cmpz(inherited) - || (poldace->flags - & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE))) { + if ((fordir || le16_cmpz(inherited) + || (poldace->flags + & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE))) + && acceptable) { pnewace = (ACCESS_ALLOWED_ACE*) ((char*)newacl + dst); memcpy(pnewace,poldace,acesz); @@ -869,7 +904,8 @@ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl, && !(poldace->flags & (CONTAINER_INHERIT_ACE | NO_PROPAGATE_INHERIT_ACE))) pnewace->flags |= INHERIT_ONLY_ACE; - if ((poldace->flags & CONTAINER_INHERIT_ACE) + if (acceptable + && (poldace->flags & CONTAINER_INHERIT_ACE) && !(poldace->flags & NO_PROPAGATE_INHERIT_ACE) && !ntfs_same_sid(&poldace->sid, ownersid) && !ntfs_same_sid(&poldace->sid, groupsid)) { @@ -3867,7 +3903,9 @@ struct POSIX_SECURITY *ntfs_build_permissions_posix( } } } - if (!ignore) { + if (((pace->type == ACCESS_ALLOWED_ACE_TYPE) + || (pace->type == ACCESS_DENIED_ACE_TYPE)) + && !ignore) { pxace->perms = 0; /* specific decoding for vtx/uid/gid */ if (pxace->tag == POSIX_ACL_SPECIAL) { diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c index 89e6f0fa..1283b6b4 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -5,7 +5,7 @@ * Copyright (c) 2002-2005 Richard Russon * Copyright (c) 2002-2008 Szabolcs Szakacsits * Copyright (c) 2004-2007 Yura Pakhuchiy - * Copyright (c) 2007-2015 Jean-Pierre Andre + * Copyright (c) 2007-2020 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -5850,6 +5850,9 @@ retry: le16_or(ATTR_IS_COMPRESSED, ATTR_IS_SPARSE)); if (!le16_cmpz(spcomp)) a->compressed_size = cpu_to_sle64(na->compressed_size); + /* Updating sizes taints the extent holding the attr */ + if (ctx->ntfs_ino) + NInoSetDirty(ctx->ntfs_ino); if (le32_eq(na->type, AT_DATA) && (na->name == AT_UNNAMED)) { na->ni->allocated_size = (!le16_cmpz(spcomp) @@ -6902,7 +6905,9 @@ s64 ntfs_attr_get_free_bits(ntfs_attr *na) } switch (br % 4) { case 3: nr_free += lut[*(buf + br - 3)]; + /* FALLTHRU */ case 2: nr_free += lut[*(buf + br - 2)]; + /* FALLTHRU */ case 1: nr_free += lut[*(buf + br - 1)]; } } diff --git a/libntfs-3g/bootsect.c b/libntfs-3g/bootsect.c index f867027e..0bc56088 100644 --- a/libntfs-3g/bootsect.c +++ b/libntfs-3g/bootsect.c @@ -38,6 +38,7 @@ #include #endif +#include "param.h" #include "compat.h" #include "bootsect.h" #include "debug.h" @@ -61,6 +62,7 @@ BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b) { u32 i; BOOL ret = FALSE; + u16 sectors_per_cluster; ntfs_log_debug("Beginning bootsector check.\n"); @@ -83,15 +85,27 @@ BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b) case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: break; default: - ntfs_log_error("Unexpected sectors per cluster value (%d).\n", - b->bpb.sectors_per_cluster); - goto not_ntfs; + if ((b->bpb.sectors_per_cluster < 240) + || (b->bpb.sectors_per_cluster > 253)) { + if (b->bpb.sectors_per_cluster > 128) + ntfs_log_error("Unexpected sectors" + " per cluster value (code 0x%x)\n", + b->bpb.sectors_per_cluster); + else + ntfs_log_error("Unexpected sectors" + " per cluster value (%d).\n", + b->bpb.sectors_per_cluster); + goto not_ntfs; + } } ntfs_log_debug("Checking cluster size.\n"); - i = (u32)le16_to_cpu(b->bpb.bytes_per_sector) * - b->bpb.sectors_per_cluster; - if (i > 65536) { + if (b->bpb.sectors_per_cluster > 128) + sectors_per_cluster = 1 << (256 - b->bpb.sectors_per_cluster); + else + sectors_per_cluster = b->bpb.sectors_per_cluster; + i = (u32)le16_to_cpu(b->bpb.bytes_per_sector) * sectors_per_cluster; + if (i > NTFS_MAX_CLUSTER_SIZE) { ntfs_log_error("Unexpected cluster size (%d).\n", i); goto not_ntfs; } @@ -140,6 +154,12 @@ BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b) } } + /* MFT and MFTMirr may not overlap the boot sector or be the same */ + if (sle64_cmpz(b->mft_lcn) || sle64_cmpz(b->mftmirr_lcn) || sle64_eq(b->mft_lcn, b->mftmirr_lcn)) { + ntfs_log_error("Invalid location of MFT or MFTMirr.\n"); + goto not_ntfs; + } + if (!le16_eq(b->end_of_sector_marker, const_cpu_to_le16(0xaa55))) ntfs_log_debug("Warning: Bootsector has invalid end of sector " "marker.\n"); @@ -171,7 +191,7 @@ static const char *last_sector_error = int ntfs_boot_sector_parse(ntfs_volume *vol, const NTFS_BOOT_SECTOR *bs) { s64 sectors; - u8 sectors_per_cluster; + u16 sectors_per_cluster; s8 c; /* We return -1 with errno = EINVAL on error. */ @@ -186,7 +206,10 @@ int ntfs_boot_sector_parse(ntfs_volume *vol, const NTFS_BOOT_SECTOR *bs) * below or equal the number_of_clusters) really belong in the * ntfs_boot_sector_is_ntfs but in this way we can just do this once. */ - sectors_per_cluster = bs->bpb.sectors_per_cluster; + if (bs->bpb.sectors_per_cluster > 128) + sectors_per_cluster = 1 << (256 - bs->bpb.sectors_per_cluster); + else + sectors_per_cluster = bs->bpb.sectors_per_cluster; ntfs_log_debug("SectorsPerCluster = 0x%x\n", sectors_per_cluster); if (sectors_per_cluster & (sectors_per_cluster - 1)) { ntfs_log_error("sectors_per_cluster (%d) is not a power of 2." diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index a9c82ea0..85b2e64e 100644 --- a/libntfs-3g/compress.c +++ b/libntfs-3g/compress.c @@ -491,6 +491,8 @@ do_next_sb: * first two checks do not detect it. */ if (cb == cb_end || !le16_to_cpup((le16*)cb) || dest == dest_end) { + if (dest_end > dest) + memset(dest, 0, dest_end - dest); ntfs_log_debug("Completed. Returning success (0).\n"); return 0; } diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 6fd36005..7f4d438a 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2004-2008 Szabolcs Szakacsits * Copyright (c) 2005-2007 Yura Pakhuchiy - * Copyright (c) 2008-2014 Jean-Pierre Andre + * Copyright (c) 2008-2021 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 @@ -68,6 +68,7 @@ #include "reparse.h" #include "object_id.h" #include "xattrs.h" +#include "ea.h" /* * The little endian Unicode strings "$I30", "$SII", "$SDH", "$O" @@ -934,9 +935,9 @@ static u32 ntfs_dir_entry_type(ntfs_inode *dir_ni, MFT_REF mref, dt_type = NTFS_DT_UNKNOWN; ni = ntfs_inode_open(dir_ni->vol, mref); if (ni) { - if (!le32_andz(attributes, FILE_ATTR_REPARSE_POINT) - && ntfs_possible_symlink(ni)) - dt_type = NTFS_DT_LNK; + if (!le32_andz(attributes, FILE_ATTR_REPARSE_POINT)) + dt_type = (ntfs_possible_symlink(ni) + ? NTFS_DT_LNK : NTFS_DT_REPARSE); else if (!le32_andz(attributes, FILE_ATTR_SYSTEM) && le32_andz(attributes, FILE_ATTR_I30_INDEX_PRESENT)) @@ -1496,9 +1497,11 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, { ntfs_inode *ni; int rollback_data = 0, rollback_sd = 0; + int rollback_dir = 0; FILE_NAME_ATTR *fn = NULL; STANDARD_INFORMATION *si = NULL; int err, fn_len, si_len; + ntfs_volume_special_files special_files; ntfs_log_trace("Entering.\n"); @@ -1508,18 +1511,14 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, errno = EINVAL; return NULL; } - - if (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { - errno = EOPNOTSUPP; - return NULL; - } - + ni = ntfs_mft_record_alloc(dir_ni->vol, NULL); if (!ni) return NULL; #if CACHE_NIDATA_SIZE ntfs_inode_invalidate(dir_ni->vol, ni->mft_no); #endif + special_files = dir_ni->vol->special_files; /* * Create STANDARD_INFORMATION attribute. * JPA Depending on available inherited security descriptor, @@ -1547,8 +1546,19 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, } else clear_nino_flag(ni, v3_Extensions); if (!S_ISREG(type) && !S_ISDIR(type)) { - si->file_attributes = FILE_ATTR_SYSTEM; - ni->flags = FILE_ATTR_SYSTEM; + switch (special_files) { + case NTFS_FILES_WSL : + if (!S_ISLNK(type)) { + si->file_attributes + = FILE_ATTRIBUTE_RECALL_ON_OPEN; + ni->flags = FILE_ATTRIBUTE_RECALL_ON_OPEN; + } + break; + default : + si->file_attributes = FILE_ATTR_SYSTEM; + ni->flags = FILE_ATTR_SYSTEM; + break; + } } ni->flags = le32_or(ni->flags, FILE_ATTR_ARCHIVE); if (NVolHideDotFiles(dir_ni->vol) @@ -1581,8 +1591,8 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, err = errno; goto err_out; } + rollback_sd = 1; } - rollback_sd = 1; if (S_ISDIR(type)) { INDEX_ROOT *ir = NULL; @@ -1631,34 +1641,58 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, switch (type) { case S_IFBLK: case S_IFCHR: - data_len = offsetof(INTX_FILE, device_end); - data = ntfs_malloc(data_len); - if (!data) { - err = errno; - goto err_out; + switch (special_files) { + case NTFS_FILES_WSL : + data_len = 0; + data = (INTX_FILE*)NULL; + break; + default : + data_len = offsetof(INTX_FILE, + device_end); + data = (INTX_FILE*)ntfs_malloc( + data_len); + if (!data) { + err = errno; + goto err_out; + } + data->major = cpu_to_le64(major(dev)); + data->minor = cpu_to_le64(minor(dev)); + if (type == S_IFBLK) + data->magic + = INTX_BLOCK_DEVICE; + if (type == S_IFCHR) + data->magic + = INTX_CHARACTER_DEVICE; + break; } - data->major = cpu_to_le64(major(dev)); - data->minor = cpu_to_le64(minor(dev)); - if (type == S_IFBLK) - data->magic = INTX_BLOCK_DEVICE; - if (type == S_IFCHR) - data->magic = INTX_CHARACTER_DEVICE; break; case S_IFLNK: - data_len = sizeof(INTX_FILE_TYPES) + + switch (special_files) { + case NTFS_FILES_WSL : + data_len = 0; + data = (INTX_FILE*)NULL; + break; + default : + data_len = sizeof(INTX_FILE_TYPES) + target_len * sizeof(ntfschar); - data = ntfs_malloc(data_len); - if (!data) { - err = errno; - goto err_out; - } - data->magic = INTX_SYMBOLIC_LINK; - memcpy(data->target, target, + data = (INTX_FILE*)ntfs_malloc( + data_len); + if (!data) { + err = errno; + goto err_out; + } + data->magic = INTX_SYMBOLIC_LINK; + memcpy(data->target, target, target_len * sizeof(ntfschar)); + break; + } break; case S_IFSOCK: data = NULL; - data_len = 1; + if (special_files == NTFS_FILES_WSL) + data_len = 0; + else + data_len = 1; break; default: /* FIFO or regular file. */ data = NULL; @@ -1689,9 +1723,10 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, fn->file_name_type = FILE_NAME_POSIX; if (S_ISDIR(type)) fn->file_attributes = FILE_ATTR_I30_INDEX_PRESENT; - if (!S_ISREG(type) && !S_ISDIR(type)) - fn->file_attributes = FILE_ATTR_SYSTEM; - else + if (!S_ISREG(type) && !S_ISDIR(type)) { + if (special_files == NTFS_FILES_INTERIX) + fn->file_attributes = FILE_ATTR_SYSTEM; + } else fn->file_attributes = le32_or(fn->file_attributes, le32_and(ni->flags, FILE_ATTR_COMPRESSED)); fn->file_attributes = le32_or(fn->file_attributes, FILE_ATTR_ARCHIVE); fn->file_attributes = le32_or(fn->file_attributes, le32_and(ni->flags, FILE_ATTR_HIDDEN)); @@ -1719,10 +1754,40 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, ntfs_log_perror("Failed to add entry to the index"); goto err_out; } + rollback_dir = 1; /* Set hard links count and directory flag. */ ni->mrec->link_count = const_cpu_to_le16(1); if (S_ISDIR(type)) ni->mrec->flags = le16_or(ni->mrec->flags, MFT_RECORD_IS_DIRECTORY); + /* Add reparse data */ + if (special_files == NTFS_FILES_WSL) { + switch (type) { + case S_IFLNK : + err = ntfs_reparse_set_wsl_symlink(ni, target, + target_len); + break; + case S_IFIFO : + case S_IFSOCK : + case S_IFCHR : + case S_IFBLK : + err = ntfs_reparse_set_wsl_not_symlink(ni, + type); + if (!err) { + err = ntfs_ea_set_wsl_not_symlink(ni, + type, dev); + if (err) + ntfs_remove_ntfs_reparse_data(ni); + } + break; + default : + err = 0; + break; + } + if (err) { + err = errno; + goto err_out; + } + } ntfs_inode_mark_dirty(ni); /* Done! */ free(fn); @@ -1732,6 +1797,9 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, err_out: ntfs_log_trace("Failed.\n"); + if (rollback_dir) + ntfs_index_remove(dir_ni, ni, fn, fn_len); + if (rollback_sd) ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); @@ -2792,3 +2860,82 @@ int ntfs_remove_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni) } return (res); } + +/* + * Increment the count of subdirectories + * (excluding entries with a short name) + */ + +static int nlink_increment(void *nlink_ptr, + const ntfschar *name __attribute__((unused)), + const int len __attribute__((unused)), + const int type, + const s64 pos __attribute__((unused)), + const MFT_REF mref __attribute__((unused)), + const unsigned int dt_type) +{ + if ((dt_type == NTFS_DT_DIR) && (type != FILE_NAME_DOS)) + (*((int*)nlink_ptr))++; + return (0); +} + +/* + * Compute the number of hard links according to Posix + * For a directory count the subdirectories whose name is not + * a short one, but count "." and ".." + * Otherwise count the names, excluding the short ones. + * + * if there is an error, a null count is returned. + */ + +int ntfs_dir_link_cnt(ntfs_inode *ni) +{ + ntfs_attr_search_ctx *actx; + FILE_NAME_ATTR *fn; + s64 pos; + int err = 0; + int nlink = 0; + + if (!ni) { + ntfs_log_error("Invalid argument.\n"); + errno = EINVAL; + goto err_out; + } + if (ni->nr_extents == -1) + ni = ni->base_ni; + if (!le16_andz(ni->mrec->flags, MFT_RECORD_IS_DIRECTORY)) { + /* + * Directory : scan the directory and count + * subdirectories whose name is not DOS-only. + * The directory names are ignored, but "." and ".." + * are taken into account. + */ + pos = 0; + err = ntfs_readdir(ni, &pos, &nlink, nlink_increment); + if (err) + nlink = 0; + } else { + /* + * Non-directory : search for FILE_NAME attributes, + * and count those which are not DOS-only ones. + */ + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) + goto err_out; + while (!(err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, + CASE_SENSITIVE, 0, NULL, 0, actx))) { + fn = (FILE_NAME_ATTR*)((u8*)actx->attr + + le16_to_cpu(actx->attr->value_offset)); + if (fn->file_name_type != FILE_NAME_DOS) + nlink++; + } + if (err && (errno != ENOENT)) + nlink = 0; + ntfs_attr_put_search_ctx(actx); + } + if (!nlink) + ntfs_log_perror("Failed to compute nlink of inode %lld", + (long long)ni->mft_no); +err_out : + return (nlink); +} diff --git a/libntfs-3g/ea.c b/libntfs-3g/ea.c index 0a7b20e9..c935fafb 100644 --- a/libntfs-3g/ea.c +++ b/libntfs-3g/ea.c @@ -3,7 +3,7 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-2021 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 @@ -43,6 +43,12 @@ #ifdef HAVE_ERRNO_H #include #endif +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif #include "types.h" #include "param.h" @@ -55,6 +61,10 @@ #include "logging.h" #include "xattrs.h" +static const char lxdev[] = "$LXDEV"; +static const char lxmod[] = "$LXMOD"; + + /* * Create a needed attribute (EA or EA_INFORMATION) * @@ -284,12 +294,11 @@ int ntfs_set_ntfs_ea(ntfs_inode *ni, const char *value, size_t size, int flags) } } /* - * EA and REPARSE_POINT exclude each other - * see http://msdn.microsoft.com/en-us/library/windows/desktop/aa364404(v=vs.85).aspx - * Also return EINVAL if REPARSE_POINT is present. + * EA and REPARSE_POINT compatibility not checked any more, + * required by Windows 10, but having both may lead to + * problems with earlier versions. */ - if (ok - && !ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED,0)) { + if (ok) { ea_info.ea_length = cpu_to_le16(ea_packed); ea_info.need_ea_count = cpu_to_le16(ea_count); ea_info.ea_query_length = cpu_to_le32(nextoffs); @@ -393,3 +402,118 @@ int ntfs_remove_ntfs_ea(ntfs_inode *ni) } return (res ? -1 : 0); } + +/* + * Check for the presence of an EA "$LXDEV" (used by WSL) + * and return its value as a device address + * + * Returns zero if successful + * -1 if failed, with errno set + */ + +int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp) +{ + const EA_ATTR *p_ea; + int bufsize; + char *buf; + int lth; + int res; + int offset; + int next; + BOOL found; + struct { + le32 major; + le32 minor; + } device; + + res = -EOPNOTSUPP; + bufsize = 256; /* expected to be enough */ + buf = (char*)malloc(bufsize); + if (buf) { + lth = ntfs_get_ntfs_ea(ni, buf, bufsize); + /* retry if short buf */ + if (lth > bufsize) { + free(buf); + bufsize = lth; + buf = (char*)malloc(bufsize); + if (buf) + lth = ntfs_get_ntfs_ea(ni, buf, bufsize); + } + } + if (buf && (lth > 0) && (lth <= bufsize)) { + offset = 0; + found = FALSE; + do { + p_ea = (const EA_ATTR*)&buf[offset]; + next = le32_to_cpu(p_ea->next_entry_offset); + found = ((next > (int)(sizeof(lxdev) + sizeof(device))) + && (p_ea->name_length == (sizeof(lxdev) - 1)) + && le16_eq(p_ea->value_length, + const_cpu_to_le16(sizeof(device))) + && !memcmp(p_ea->name, lxdev, sizeof(lxdev))); + if (!found) + offset += next; + } while (!found && (next > 0) && (offset < lth)); + if (found) { + /* beware of alignment */ + memcpy(&device, &p_ea->name[p_ea->name_length + 1], + sizeof(device)); + *rdevp = makedev(le32_to_cpu(device.major), + le32_to_cpu(device.minor)); + res = 0; + } + } + free(buf); + return (res); +} + +int ntfs_ea_set_wsl_not_symlink(ntfs_inode *ni, mode_t type, dev_t dev) +{ + le32 mode; + struct { + le32 major; + le32 minor; + } device; + struct EA_WSL { + struct EA_LXMOD { /* always inserted */ + EA_ATTR base; + char name[sizeof(lxmod)]; + char value[sizeof(mode)]; + char stuff[3 & -(sizeof(lxmod) + sizeof(mode))]; + } mod; + struct EA_LXDEV { /* char or block devices only */ + EA_ATTR base; + char name[sizeof(lxdev)]; + char value[sizeof(device)]; + char stuff[3 & -(sizeof(lxdev) + sizeof(device))]; + } dev; + } attr; + int len; + int res; + + memset(&attr, 0, sizeof(attr)); + mode = cpu_to_le32((u32)(type | 0644)); + attr.mod.base.next_entry_offset + = const_cpu_to_le32(sizeof(attr.mod)); + attr.mod.base.flags = 0; + attr.mod.base.name_length = sizeof(lxmod) - 1; + attr.mod.base.value_length = const_cpu_to_le16(sizeof(mode)); + memcpy(attr.mod.name, lxmod, sizeof(lxmod)); + memcpy(attr.mod.value, &mode, sizeof(mode)); + len = sizeof(attr.mod); + + if (S_ISCHR(type) || S_ISBLK(type)) { + device.major = cpu_to_le32(major(dev)); + device.minor = cpu_to_le32(minor(dev)); + attr.dev.base.next_entry_offset + = const_cpu_to_le32(sizeof(attr.dev)); + attr.dev.base.flags = 0; + attr.dev.base.name_length = sizeof(lxdev) - 1; + attr.dev.base.value_length = const_cpu_to_le16(sizeof(device)); + memcpy(attr.dev.name, lxdev, sizeof(lxdev)); + memcpy(attr.dev.value, &device, sizeof(device)); + len += sizeof(attr.dev); + } + res = ntfs_set_ntfs_ea(ni, (char*)&attr, len, 0); + return (res); +} diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index a5020187..7db395a0 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2005-2006 Yura Pakhuchiy * Copyright (c) 2005-2008 Szabolcs Szakacsits - * Copyright (c) 2007 Jean-Pierre Andre + * Copyright (c) 2007-2020 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 @@ -1563,19 +1563,32 @@ static int ntfs_ih_takeout(ntfs_index_context *icx, INDEX_HEADER *ih, INDEX_ENTRY *ie, INDEX_BLOCK *ib) { INDEX_ENTRY *ie_roam; + int freed_space; + BOOL full; int ret = STATUS_ERROR; ntfs_log_trace("Entering\n"); + full = le32_eq(ih->index_length, ih->allocated_size); ie_roam = ntfs_ie_dup_novcn(ie); if (!ie_roam) return STATUS_ERROR; ntfs_ie_delete(ih, ie); - if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) + if (ntfs_icx_parent_vcn(icx) == VCN_INDEX_ROOT_PARENT) { + /* + * Recover the space which may have been freed + * while deleting an entry from root index + */ + freed_space = le32_to_cpu(ih->allocated_size) + - le32_to_cpu(ih->index_length); + if (full && (freed_space > 0) && !(freed_space & 7)) { + ntfs_ir_truncate(icx, le32_to_cpu(ih->index_length)); + /* do nothing if truncation fails */ + } ntfs_inode_mark_dirty(icx->actx->ntfs_ino); - else + } else if (ntfs_ib_write(icx, ib)) goto out; diff --git a/libntfs-3g/inode.c b/libntfs-3g/inode.c index 6562a2be..6f853740 100644 --- a/libntfs-3g/inode.c +++ b/libntfs-3g/inode.c @@ -837,7 +837,7 @@ static int ntfs_inode_sync_file_name(ntfs_inode *ni, ntfs_inode *dir_ni) if (!err) err = errno; ntfs_log_perror("Failed to open inode %lld with index", - (long long)le64_to_cpu(fn->parent_directory)); + (long long)MREF_LE(fn->parent_directory)); continue; } ictx = ntfs_index_ctx_get(index_ni, NTFS_INDEX_I30, 4); @@ -1518,14 +1518,16 @@ int ntfs_inode_set_times(ntfs_inode *ni, const char *value, size_t size, ntfs_attr_search_ctx *ctx; STANDARD_INFORMATION *std_info; FILE_NAME_ATTR *fn; - const u64 *times; + u64 times[4]; ntfs_time now; int cnt; int ret; ret = -1; if ((size >= 8) && !(flags & XATTR_CREATE)) { - times = (const u64*)value; + /* Copy, to avoid alignment issue encountered on ARM */ + memcpy(times, value, + (size < sizeof(times) ? size : sizeof(times))); now = ntfs_current_time(); /* update the standard information attribute */ ctx = ntfs_attr_get_search_ctx(ni, NULL); diff --git a/libntfs-3g/ioctl.c b/libntfs-3g/ioctl.c index 2eef5a51..b059a53f 100644 --- a/libntfs-3g/ioctl.c +++ b/libntfs-3g/ioctl.c @@ -3,7 +3,7 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2014-2015 Jean-Pierre Andre + * Copyright (c) 2014-2019 Jean-Pierre Andre * Copyright (c) 2014 Red Hat, Inc. * * This program/include file is free software; you can redistribute it and/or @@ -141,7 +141,8 @@ static int fstrim_limits(ntfs_volume *vol, u64 *discard_max_bytes) { struct stat statbuf; - char path1[80], path2[80]; + char path1[40]; /* holds "/sys/dev/block/%d:%d" */ + char path2[40 + sizeof(path1)]; /* less than 40 bytes more than path1 */ int ret; /* Stat the backing device. Caller has ensured it is a block device. */ @@ -225,6 +226,24 @@ not_found: return 0; } +static inline LCN align_up(ntfs_volume *vol, LCN lcn, u64 granularity) +{ + u64 aligned; + + aligned = (lcn << vol->cluster_size_bits) + granularity - 1; + aligned -= aligned % granularity; + return (aligned >> vol->cluster_size_bits); +} + +static inline u64 align_down(ntfs_volume *vol, u64 count, u64 granularity) +{ + u64 aligned; + + aligned = count << vol->cluster_size_bits; + aligned -= aligned % granularity; + return (aligned >> vol->cluster_size_bits); +} + #define FSTRIM_BUFSIZ 4096 /* Trim the filesystem. @@ -255,11 +274,11 @@ static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) * XXX We could fix these limitations in future. */ if (start != 0 || len != (uint64_t)-1) { - ntfs_log_debug("fstrim: setting start or length is not supported\n"); + ntfs_log_error("fstrim: setting start or length is not supported\n"); return -EINVAL; } if (minlen > vol->cluster_size) { - ntfs_log_debug("fstrim: minlen > cluster size is not supported\n"); + ntfs_log_error("fstrim: minlen > cluster size is not supported\n"); return -EINVAL; } @@ -269,7 +288,7 @@ static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) * different. */ if (!NDevBlock(vol->dev)) { - ntfs_log_debug("fstrim: not supported for non-block-device\n"); + ntfs_log_error("fstrim: not supported for non-block-device\n"); return -EOPNOTSUPP; } @@ -278,15 +297,12 @@ static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) if (ret) return ret; if (discard_alignment != 0) { - ntfs_log_debug("fstrim: backing device is not aligned for discards\n"); - return -EOPNOTSUPP; - } - if (discard_granularity > vol->cluster_size) { - ntfs_log_debug("fstrim: discard granularity of backing device is larger than cluster size\n"); + ntfs_log_error("fstrim: backing device is not aligned for discards\n"); return -EOPNOTSUPP; } + if (discard_max_bytes == 0) { - ntfs_log_debug("fstrim: backing device does not support discard (discard_max_bytes == 0)\n"); + ntfs_log_error("fstrim: backing device does not support discard (discard_max_bytes == 0)\n"); return -EOPNOTSUPP; } @@ -323,11 +339,14 @@ static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) } /* Trim the clusters in large as possible blocks, but - * not larger than discard_max_bytes. + * not larger than discard_max_bytes, and compatible + * with the supported trim granularity. */ for (start_lcn = start_buf; start_lcn < end_buf; ++start_lcn) { if (!ntfs_bit_get(buf, start_lcn-start_buf)) { LCN end_lcn; + LCN aligned_lcn; + u64 aligned_count; /* Cluster 'start_lcn' is not in use, * find end of this run. @@ -338,14 +357,25 @@ static int fstrim(ntfs_volume *vol, void *data, u64 *trimmed) < discard_max_bytes && !ntfs_bit_get(buf, end_lcn-start_buf)) end_lcn++; + aligned_lcn = align_up(vol, start_lcn, + discard_granularity); + if (aligned_lcn >= end_lcn) + aligned_count = 0; + else { + aligned_count = + align_down(vol, + end_lcn - aligned_lcn, + discard_granularity); + } + if (aligned_count) { + ret = fstrim_clusters(vol, + aligned_lcn, aligned_count); + if (ret) + goto free_out; - ret = fstrim_clusters(vol, - start_lcn, end_lcn-start_lcn); - if (ret) - goto free_out; - - *trimmed += (end_lcn - start_lcn) + *trimmed += aligned_count << vol->cluster_size_bits; + } start_lcn = end_lcn-1; } } @@ -359,7 +389,8 @@ free_out: #endif /* FITRIM && BLKDISCARD */ -int ntfs_ioctl(ntfs_inode *ni, int cmd, void *arg __attribute__((unused)), +int ntfs_ioctl(ntfs_inode *ni, unsigned long cmd, + void *arg __attribute__((unused)), unsigned int flags __attribute__((unused)), void *data) { int ret = 0; diff --git a/libntfs-3g/mft.c b/libntfs-3g/mft.c index 0076621b..9d86acb2 100644 --- a/libntfs-3g/mft.c +++ b/libntfs-3g/mft.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2004-2008 Szabolcs Szakacsits * Copyright (c) 2005 Yura Pakhuchiy - * Copyright (c) 2014-2015 Jean-Pierre Andre + * Copyright (c) 2014-2018 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 @@ -1389,16 +1389,27 @@ ntfs_inode *ntfs_mft_rec_alloc(ntfs_volume *vol, BOOL mft_data) */ if (ext_ni) { /* - * Make sure record 15 is a base extent and has - * no extents. - * Also make sure it has no name : a base inode with - * no extents and no name cannot be in use. - * Otherwise apply standard procedure. + * Make sure record 15 is a base extent and it has + * no name. A base inode with no name cannot be in use. + * The test based on base_mft_record fails for + * extents of MFT, so we need a special check. + * If already used, apply standard procedure. */ if (le64_cmpz(ext_ni->mrec->base_mft_record) - && !ext_ni->nr_extents) + && le16_cmpz(ext_ni->mrec->link_count)) forced_mft_data = TRUE; ntfs_inode_close(ext_ni); + /* Double-check, in case it is used for MFT */ + if (forced_mft_data && base_ni->nr_extents) { + int i; + + for (i=0; inr_extents; i++) { + if (base_ni->extent_nis[i] + && (base_ni->extent_nis[i]->mft_no + == FILE_mft_data)) + forced_mft_data = FALSE; + } + } } } if (forced_mft_data) diff --git a/libntfs-3g/misc.c b/libntfs-3g/misc.c index b2e17cbf..03fb592a 100644 --- a/libntfs-3g/misc.c +++ b/libntfs-3g/misc.c @@ -59,3 +59,19 @@ void *ntfs_malloc(size_t size) ntfs_log_perror("Failed to malloc %lld bytes", (long long)size); return p; } + +void *ntfs_realloc(void *ptr, size_t size) +{ + void *p; + + p = realloc(ptr, size); + if (!p) + ntfs_log_perror("Failed to realloc %lld bytes", + (long long)size); + return p; +} + +void ntfs_free(void *p) +{ + free(p); +} diff --git a/libntfs-3g/object_id.c b/libntfs-3g/object_id.c index bd72d026..090070f3 100644 --- a/libntfs-3g/object_id.c +++ b/libntfs-3g/object_id.c @@ -3,7 +3,7 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2009 Jean-Pierre Andre + * Copyright (c) 2009-2019 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 @@ -63,9 +63,13 @@ * significant byte first, and the six fields be compared individually * for ordering. RFC 4122 does not define the internal representation. * + * Windows apparently stores the first three fields in little endian + * order, and the last two fields in big endian order. + * * Here we always copy disk images with no endianness change, * and, for indexing, GUIDs are compared as if they were a sequence - * of four unsigned 32 bit integers. + * of four little-endian unsigned 32 bit integers (as Windows + * does it that way.) * * --------------------- begin from RFC 4122 ---------------------- * Consider each field of the UUID to be an unsigned integer as shown @@ -329,7 +333,7 @@ static int update_object_id(ntfs_inode *ni, ntfs_index_context *xo, na = ntfs_attr_open(ni, AT_OBJECT_ID, AT_UNNAMED, 0); if (na) { - + memset(&old_attr, 0, sizeof(OBJECT_ID_ATTR)); /* remove the existing index entry */ oldsize = remove_object_id_index(na,xo,&old_attr); if (oldsize < 0) @@ -349,10 +353,12 @@ static int update_object_id(ntfs_inode *ni, ntfs_index_context *xo, res = -1; } } - /* write index part if provided */ + /* overwrite index data with new value */ + memcpy(&old_attr, value, + (size < sizeof(OBJECT_ID_ATTR) + ? size : sizeof(OBJECT_ID_ATTR))); if (!res - && ((size < sizeof(OBJECT_ID_ATTR)) - || set_object_id_index(ni,xo,value))) { + && set_object_id_index(ni,xo,&old_attr)) { /* * If cannot index, try to remove the object * id and log the error. There will be an @@ -500,9 +506,11 @@ int ntfs_get_ntfs_object_id(ntfs_inode *ni, char *value, size_t size) /* * Set the object id from an extended attribute * - * If the size is 64, the attribute and index are set. - * else if the size is not less than 16 only the attribute is set. - * The object id index is set accordingly. + * The first 16 bytes are the new object id, they can be followed + * by the birth volume id, the birth object id and the domain id. + * If they are not present, their previous value is kept. + * Only the object id is stored into the attribute, all the fields + * are stored into the index. * * Returns 0, or -1 if there is a problem */ @@ -519,10 +527,12 @@ int ntfs_set_ntfs_object_id(ntfs_inode *ni, if (ni && value && (size >= sizeof(GUID))) { xo = open_object_id_index(ni->vol); if (xo) { - /* make sure the GUID was not used somewhere */ + /* make sure the GUID was not used elsewhere */ memcpy(&key.object_id, value, sizeof(GUID)); - if (ntfs_index_lookup(&key, - sizeof(OBJECT_ID_INDEX_KEY), xo)) { + if ((ntfs_index_lookup(&key, + sizeof(OBJECT_ID_INDEX_KEY), xo)) + || (MREF_LE(((struct OBJECT_ID_INDEX*)xo->entry) + ->data.file_id) == ni->mft_no)) { ntfs_index_ctx_reinit(xo); res = add_object_id(ni, flags); if (!res) { diff --git a/libntfs-3g/realpath.c b/libntfs-3g/realpath.c index a93bc698..d56667e4 100644 --- a/libntfs-3g/realpath.c +++ b/libntfs-3g/realpath.c @@ -45,8 +45,8 @@ canonicalize_dm_name(const char *ptname, char *canonical) { FILE *f; size_t sz; - char path[MAPPERNAMELTH + 24]; char name[MAPPERNAMELTH + 16]; + char path[sizeof(name) + 16]; char *res = NULL; snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname); diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index 177a61e8..c13eb5d5 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-2016 Jean-Pierre Andre + * Copyright (c) 2008-2021 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 @@ -37,7 +37,10 @@ #ifdef HAVE_SYS_STAT_H #include #endif -#ifdef HAVE_SYS_SYSMACROS_H +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS #include #endif @@ -56,6 +59,7 @@ #include "misc.h" #include "reparse.h" #include "xattrs.h" +#include "ea.h" struct MOUNT_POINT_REPARSE_DATA { /* reparse data for junctions */ le16 subst_name_offset; @@ -74,6 +78,11 @@ struct SYMLINK_REPARSE_DATA { /* reparse data for symlinks */ char path_buffer[0]; /* above data assume this is char array */ } ; +struct WSL_LINK_REPARSE_DATA { + le32 type; + char link[0]; +} ; + struct REPARSE_INDEX { /* index entry in $Extend/$Reparse */ INDEX_ENTRY_HEADER header; REPARSE_INDEX_KEY key; @@ -415,6 +424,35 @@ static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter) return (ret); } +/* + * Check whether reparse data describes a valid wsl special file + * which is either a socket, a fifo, or a character or block device + * + * Return zero if valid, otherwise returns a negative error code + */ + +int ntfs_reparse_check_wsl(ntfs_inode *ni, const REPARSE_POINT *reparse) +{ + int res; + + res = -EOPNOTSUPP; + /* switch (reparse->reparse_tag) { */ + if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_AF_UNIX) || + le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_FIFO) || + le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_CHR) || + le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_BLK)) { + if (le16_cmpz(reparse->reparse_data_length) + && !le32_andz(ni->flags, FILE_ATTRIBUTE_RECALL_ON_OPEN)) + res = 0; + } + else { + } + /* } */ + if (res) + errno = EOPNOTSUPP; + return (res); +} + /* * Do some sanity checks on reparse data * @@ -435,6 +473,7 @@ static BOOL valid_reparse_data(ntfs_inode *ni, unsigned int lth; const struct MOUNT_POINT_REPARSE_DATA *mount_point_data; const struct SYMLINK_REPARSE_DATA *symlink_data; + const struct WSL_LINK_REPARSE_DATA *wsl_reparse_data; ok = ni && reparse_attr && (size >= sizeof(REPARSE_POINT)) @@ -477,7 +516,24 @@ static BOOL valid_reparse_data(ntfs_inode *ni, + offs + lth)) > size) ok = FALSE; break; - } else { + } else if (le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_LX_SYMLINK)) { + wsl_reparse_data = (const struct WSL_LINK_REPARSE_DATA*) + reparse_attr->reparse_data; + if ((le16_to_cpu(reparse_attr->reparse_data_length) + <= sizeof(wsl_reparse_data->type)) + || (!le32_eq(wsl_reparse_data->type, const_cpu_to_le32(2)))) + ok = FALSE; + break; + } else if (le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_AF_UNIX) || + le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_LX_FIFO) || + le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_LX_CHR) || + le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_LX_BLK)) { + if (!le16_cmpz(reparse_attr->reparse_data_length) + || le32_andz(ni->flags, FILE_ATTRIBUTE_RECALL_ON_OPEN)) + ok = FALSE; + break; + } + else { break; } } while(0); } @@ -604,8 +660,9 @@ static char *ntfs_get_fulllink(ntfs_volume *vol, ntfschar *junction, * or NULL if there were some problem, as described by errno */ -static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, - int count, const char *mnt_point, BOOL isdir) +char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, int count, + const char *mnt_point __attribute__((unused)), + BOOL isdir) { char *target; char *fulltarget; @@ -651,10 +708,11 @@ static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, target = search_absolute(vol, &junction[3], count - 3, isdir); if (target) { - fulltarget = (char*)ntfs_malloc(strlen(mnt_point) + fulltarget = (char*)ntfs_malloc( + strlen(vol->abs_mnt_point) + strlen(target) + 2); if (fulltarget) { - strcpy(fulltarget,mnt_point); + strcpy(fulltarget,vol->abs_mnt_point); strcat(fulltarget,"/"); strcat(fulltarget,target); } @@ -679,10 +737,11 @@ static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, && (target[0] >= 'a') && (target[0] <= 'z')) target[0] += 'A' - 'a'; - fulltarget = (char*)ntfs_malloc(strlen(mnt_point) + fulltarget = (char*)ntfs_malloc( + strlen(vol->abs_mnt_point) + sizeof(mappingdir) + strlen(target) + 1); if (fulltarget) { - strcpy(fulltarget,mnt_point); + strcpy(fulltarget,vol->abs_mnt_point); strcat(fulltarget,"/"); strcat(fulltarget,mappingdir); strcat(fulltarget,target); @@ -734,6 +793,7 @@ char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point) REPARSE_POINT *reparse_attr; struct MOUNT_POINT_REPARSE_DATA *mount_point_data; struct SYMLINK_REPARSE_DATA *symlink_data; + struct WSL_LINK_REPARSE_DATA *wsl_link_data; enum { FULL_TARGET, ABS_TARGET, REL_TARGET } kind; ntfschar *p; BOOL bad; @@ -815,7 +875,22 @@ char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point) } break; } - } else { + } + else if (le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_LX_SYMLINK)) { + wsl_link_data = (struct WSL_LINK_REPARSE_DATA*) + reparse_attr->reparse_data; + if (le32_eq(wsl_link_data->type, const_cpu_to_le32(2))) { + lth = le16_to_cpu( + reparse_attr->reparse_data_length) + - sizeof(wsl_link_data->type); + target = (char*)ntfs_malloc(lth + 1); + if (target) { + memcpy(target, wsl_link_data->link, + lth); + target[lth] = 0; + bad = FALSE; + } + } } free(reparse_attr); } @@ -844,7 +919,8 @@ BOOL ntfs_possible_symlink(ntfs_inode *ni) if (reparse_attr && attr_size) { /* switch (reparse_attr->reparse_tag) { */ if (le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_MOUNT_POINT) || - le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_SYMLINK)) { + le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_SYMLINK) || + le32_eq(reparse_attr->reparse_tag, IO_REPARSE_TAG_LX_SYMLINK)) { possible = TRUE; } else { } @@ -1121,11 +1197,12 @@ int ntfs_set_ntfs_reparse_data(ntfs_inode *ni, ntfs_index_context *xr; res = 0; - /* reparse data is not compatible with EA */ - if (ni - && !ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0) - && !ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0) - && valid_reparse_data(ni, (const REPARSE_POINT*)value, size)) { + /* + * reparse data compatibily with EA is not checked + * any more, it is required by Windows 10, but may + * lead to problems with earlier versions. + */ + if (ni && valid_reparse_data(ni, (const REPARSE_POINT*)value, size)) { xr = open_reparse_index(ni->vol); if (xr) { if (!ntfs_attr_exist(ni,AT_REPARSE_POINT, @@ -1254,6 +1331,92 @@ int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni) return (res ? -1 : 0); } +/* + * Set reparse data for a WSL type symlink + */ + +int ntfs_reparse_set_wsl_symlink(ntfs_inode *ni, + const ntfschar *target, int target_len) +{ + int res; + int len; + int reparse_len; + char *utarget; + REPARSE_POINT *reparse; + struct WSL_LINK_REPARSE_DATA *data; + + res = -1; + utarget = (char*)NULL; + len = ntfs_ucstombs(target, target_len, &utarget, 0); + if (len > 0) { + reparse_len = sizeof(REPARSE_POINT) + sizeof(data->type) + len; + reparse = (REPARSE_POINT*)malloc(reparse_len); + if (reparse) { + data = (struct WSL_LINK_REPARSE_DATA*) + reparse->reparse_data; + reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; + reparse->reparse_data_length + = cpu_to_le16(sizeof(data->type) + len); + reparse->reserved = const_cpu_to_le16(0); + data->type = const_cpu_to_le32(2); + memcpy(data->link, utarget, len); + res = ntfs_set_ntfs_reparse_data(ni, + (char*)reparse, reparse_len, 0); + free(reparse); + } + } + free(utarget); + return (res); +} + +/* + * Set reparse data for a WSL special file other than a symlink + * (socket, fifo, character or block device) + */ + +int ntfs_reparse_set_wsl_not_symlink(ntfs_inode *ni, mode_t mode) +{ + int res; + int len; + int reparse_len; + le32 reparse_tag; + REPARSE_POINT *reparse; + + res = -1; + len = 0; + switch (mode) { + case S_IFSOCK : + reparse_tag = IO_REPARSE_TAG_AF_UNIX; + break; + case S_IFIFO : + reparse_tag = IO_REPARSE_TAG_LX_FIFO; + break; + case S_IFCHR : + reparse_tag = IO_REPARSE_TAG_LX_CHR; + break; + case S_IFBLK : + reparse_tag = IO_REPARSE_TAG_LX_BLK; + break; + default : + len = -1; + errno = EOPNOTSUPP; + break; + } + if (len >= 0) { + reparse_len = sizeof(REPARSE_POINT) + len; + reparse = (REPARSE_POINT*)malloc(reparse_len); + if (reparse) { + reparse->reparse_tag = reparse_tag; + reparse->reparse_data_length = cpu_to_le16(len); + reparse->reserved = const_cpu_to_le16(0); + res = ntfs_set_ntfs_reparse_data(ni, + (char*)reparse, reparse_len, 0); + free(reparse); + } + } + return (res); +} + /* * Get the reparse data into a buffer @@ -1276,7 +1439,7 @@ REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni) && !valid_reparse_data(ni, reparse_attr, attr_size)) { free(reparse_attr); reparse_attr = (REPARSE_POINT*)NULL; - errno = ENOENT; + errno = EINVAL; } } else errno = EINVAL; diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index e7d4eb70..13f1f31d 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -3048,13 +3048,13 @@ BOOL ntfs_allowed_as_owner(struct SECURITY_CONTEXT *scx, ntfs_inode *ni) free(oldattr); } } - allowed = FALSE; - if (gotowner) { /* TODO : use CAP_FOWNER process capability */ - if (!processuid || (processuid == uid)) - allowed = TRUE; - else - errno = EPERM; + if (gotowner + && (!processuid || (processuid == uid))) + allowed = TRUE; + else { + allowed = FALSE; + errno = EPERM; } } return (allowed); diff --git a/libntfs-3g/unix_io.c b/libntfs-3g/unix_io.c index 64b41d3e..5495a6a4 100644 --- a/libntfs-3g/unix_io.c +++ b/libntfs-3g/unix_io.c @@ -53,6 +53,9 @@ #ifdef HAVE_LINUX_FD_H #include #endif +#ifdef HAVE_LINUX_FS_H +#include +#endif #include "types.h" #include "mst.h" @@ -140,8 +143,26 @@ static int ntfs_device_unix_io_open(struct ntfs_device *dev, int flags) *(int*)dev->d_private = open(dev->d_name, flags); if (*(int*)dev->d_private == -1) { err = errno; + /* if permission error and rw, retry read-only */ + if ((err == EACCES) && ((flags & O_RDWR) == O_RDWR)) + err = EROFS; goto err_out; } +#ifdef HAVE_LINUX_FS_H + /* Check whether the device was forced read-only */ + if (NDevBlock(dev) && ((flags & O_RDWR) == O_RDWR)) { + int r; + int state; + + r = ioctl(DEV_FD(dev), BLKROGET, &state); + if (!r && state) { + err = EROFS; + if (close(DEV_FD(dev))) + err = errno; + goto err_out; + } + } +#endif if ((flags & O_RDWR) != O_RDWR) NDevSetReadOnly(dev); @@ -348,8 +369,8 @@ static int ntfs_device_unix_io_stat(struct ntfs_device *dev, struct stat *buf) * * Returns: */ -static int ntfs_device_unix_io_ioctl(struct ntfs_device *dev, int request, - void *argp) +static int ntfs_device_unix_io_ioctl(struct ntfs_device *dev, + unsigned long request, void *argp) { return ioctl(DEV_FD(dev), request, argp); } diff --git a/libntfs-3g/volume.c b/libntfs-3g/volume.c index 2c7e4fc4..f1b1b012 100644 --- a/libntfs-3g/volume.c +++ b/libntfs-3g/volume.c @@ -529,7 +529,7 @@ ntfs_volume *ntfs_volume_startup(struct ntfs_device *dev, dev->d_name); goto error_exit; } else { - ntfs_log_info("Can only open '%s' as read-only\n", + ntfs_log_info("Error opening '%s' read-write\n", dev->d_name); NVolSetReadOnly(vol); } @@ -959,7 +959,8 @@ ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags) vol->mftmirr_size = l; } ntfs_log_debug("Comparing $MFTMirr to $MFT...\n"); - for (i = 0; i < vol->mftmirr_size; ++i) { + /* Windows 10 does not update the full $MFTMirr any more */ + for (i = 0; (i < vol->mftmirr_size) && (i < FILE_first_user); ++i) { MFT_RECORD *mrec, *mrec2; const char *ESTR[12] = { "$MFT", "$MFTMirr", "$LogFile", "$Volume", "$AttrDef", "root directory", "$Bitmap", diff --git a/libntfs-3g/win32_io.c b/libntfs-3g/win32_io.c index e5cfb640..9c72dee3 100644 --- a/libntfs-3g/win32_io.c +++ b/libntfs-3g/win32_io.c @@ -1847,14 +1847,14 @@ static __inline__ int ntfs_win32_blksszget(struct ntfs_device *dev,int *argp) return -1; } -static int ntfs_device_win32_ioctl(struct ntfs_device *dev, int request, - void *argp) +static int ntfs_device_win32_ioctl(struct ntfs_device *dev, + unsigned long request, void *argp) { #if defined(BLKGETSIZE) | defined(BLKGETSIZE64) win32_fd *fd = (win32_fd *)dev->d_private; #endif - ntfs_log_trace("win32_ioctl(%d) called.\n", request); + ntfs_log_trace("win32_ioctl(0x%lx) called.\n", request); switch (request) { #if defined(BLKGETSIZE) case BLKGETSIZE: @@ -1897,7 +1897,7 @@ static int ntfs_device_win32_ioctl(struct ntfs_device *dev, int request, return 0; #endif default: - ntfs_log_debug("unimplemented ioctl %d.\n", request); + ntfs_log_debug("unimplemented ioctl 0x%lx.\n", request); errno = EOPNOTSUPP; return -1; } diff --git a/libntfs-3g/xattrs.c b/libntfs-3g/xattrs.c index 2b7e7091..4e410de0 100644 --- a/libntfs-3g/xattrs.c +++ b/libntfs-3g/xattrs.c @@ -470,6 +470,15 @@ void ntfs_xattr_free_mapping(struct XATTRMAPPING *mapping) #endif /* XATTR_MAPPINGS */ +/* + * Get an NTFS attribute into an extended attribute + * + * Returns the non-negative size of attribute if successful, + * or negative, with errno set, when fails + * Note : the size is returned even if no buffer is provided + * for returning the attribute, or if it is zero-sized. + */ + int ntfs_xattr_system_getxattr(struct SECURITY_CONTEXT *scx, enum SYSTEMXATTRS attr, ntfs_inode *ni, ntfs_inode *dir_ni, @@ -483,12 +492,6 @@ int ntfs_xattr_system_getxattr(struct SECURITY_CONTEXT *scx, #endif #endif - /* - * the returned value is the needed - * size. If it is too small, no copy - * is done, and the caller has to - * issue a new call with correct size. - */ switch (attr) { case XATTR_NTFS_ACL : res = ntfs_get_ntfs_acl(scx, ni, value, size); @@ -596,6 +599,13 @@ int ntfs_xattr_system_getxattr(struct SECURITY_CONTEXT *scx, return (res); } +/* + * Set an NTFS attribute from an extended attribute + * + * Returns 0 if successful, + * non-zero, with errno set, when fails + */ + int ntfs_xattr_system_setxattr(struct SECURITY_CONTEXT *scx, enum SYSTEMXATTRS attr, ntfs_inode *ni, ntfs_inode *dir_ni, @@ -669,8 +679,10 @@ int ntfs_xattr_system_setxattr(struct SECURITY_CONTEXT *scx, case XATTR_NTFS_EFSINFO : if (ni->vol->efs_raw) res = ntfs_set_efs_info(ni, value, size, flags); - else + else { + errno = EPERM; res = -EPERM; + } break; case XATTR_NTFS_REPARSE_DATA : res = ntfs_set_ntfs_reparse_data(ni, value, size, flags); @@ -683,8 +695,10 @@ int ntfs_xattr_system_setxattr(struct SECURITY_CONTEXT *scx, /* warning : this closes both inodes */ res = ntfs_set_ntfs_dos_name(ni, dir_ni, value, size, flags); - else + else { + errno = EINVAL; res = -errno; + } break; case XATTR_NTFS_TIMES: res = ntfs_inode_set_times(ni, value, size, flags); diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index f4f9d1b6..08228322 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -165,8 +165,8 @@ extras: libs $(EXTRA_PROGRAMS) if ENABLE_MOUNT_HELPER install-exec-hook: - $(INSTALL) -d $(DESTDIR)/sbin - $(LN_S) -f $(sbindir)/mkntfs $(DESTDIR)/sbin/mkfs.ntfs + $(INSTALL) -d $(DESTDIR)/$(sbindir) + $(LN_S) -f $(sbindir)/mkntfs $(DESTDIR)$(sbindir)/mkfs.ntfs install-data-hook: $(INSTALL) -d $(DESTDIR)$(man8dir) diff --git a/ntfsprogs/mkntfs.8.in b/ntfsprogs/mkntfs.8.in index a83cab9c..13a17af2 100644 --- a/ntfsprogs/mkntfs.8.in +++ b/ntfsprogs/mkntfs.8.in @@ -132,7 +132,7 @@ actual writing to the device. .TP \fB\-c\fR, \fB\-\-cluster\-size\fR BYTES Specify the size of clusters in bytes. Valid cluster size values are powers of -two, with at least 256, and at most 65536 bytes per cluster. If omitted, +two, with at least 256, and at most 2097152 bytes (2MB) per cluster. If omitted, .B mkntfs uses 4096 bytes as the default cluster size. .sp diff --git a/ntfsprogs/mkntfs.c b/ntfsprogs/mkntfs.c index 39b97847..b8d65565 100644 --- a/ntfsprogs/mkntfs.c +++ b/ntfsprogs/mkntfs.c @@ -6,7 +6,7 @@ * Copyright (c) 2002-2006 Szabolcs Szakacsits * Copyright (c) 2005 Erik Sornes * Copyright (c) 2007 Yura Pakhuchiy - * Copyright (c) 2010-2014 Jean-Pierre Andre + * Copyright (c) 2010-2018 Jean-Pierre Andre * * This utility will create an NTFS 1.2 or 3.1 volume on a user * specified (block) device. @@ -119,6 +119,7 @@ # endif #endif +#include "param.h" #include "security.h" #include "types.h" #include "attrib.h" @@ -287,7 +288,7 @@ static void mkntfs_version(void) ntfs_log_info("Copyright (c) 2002-2006 Szabolcs Szakacsits\n"); ntfs_log_info("Copyright (c) 2005 Erik Sornes\n"); ntfs_log_info("Copyright (c) 2007 Yura Pakhuchiy\n"); - ntfs_log_info("Copyright (c) 2010-2014 Jean-Pierre Andre\n"); + ntfs_log_info("Copyright (c) 2010-2018 Jean-Pierre Andre\n"); ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); } @@ -669,7 +670,7 @@ static int mkntfs_parse_options(int argc, char *argv[], struct mkntfs_options *o break; case 'L': if (!opts2->label) { - opts2->label = argv[optind-1]; + opts2->label = optarg; } else { ntfs_log_error("You may only specify the label " "once.\n"); @@ -3719,11 +3720,11 @@ static BOOL mkntfs_override_vol_params(ntfs_volume *vol) /* * For huge volumes, grow the cluster size until the number of * clusters fits into 32 bits or the cluster size exceeds the - * maximum limit of 64kiB. + * maximum limit of NTFS_MAX_CLUSTER_SIZE. */ while (volume_size >> (ffs(vol->cluster_size) - 1 + 32)) { vol->cluster_size <<= 1; - if (vol->cluster_size > 65535) { + if (vol->cluster_size >= NTFS_MAX_CLUSTER_SIZE) { ntfs_log_error("Device is too large to hold an " "NTFS volume (maximum size is " "256TiB).\n"); @@ -3744,15 +3745,18 @@ static BOOL mkntfs_override_vol_params(ntfs_volume *vol) "to, or larger than, the sector size.\n"); return FALSE; } - if (vol->cluster_size > 128 * (u32)opts.sector_size) { + /* Before Windows 10 Creators, the limit was 128 */ + if (vol->cluster_size > 4096 * (u32)opts.sector_size) { ntfs_log_error("The cluster size is invalid. It cannot be " - "more that 128 times the size of the sector " + "more that 4096 times the size of the sector " "size.\n"); return FALSE; } - if (vol->cluster_size > 65536) { + if (vol->cluster_size > NTFS_MAX_CLUSTER_SIZE) { ntfs_log_error("The cluster size is invalid. The maximum " - "cluster size is 65536 bytes (64kiB).\n"); + "cluster size is %lu bytes (%lukiB).\n", + (unsigned long)NTFS_MAX_CLUSTER_SIZE, + (unsigned long)(NTFS_MAX_CLUSTER_SIZE >> 10)); return FALSE; } vol->cluster_size_bits = ffs(vol->cluster_size) - 1; @@ -4387,6 +4391,7 @@ static BOOL mkntfs_create_root_structures(void) u8 *sd; FILE_ATTR_FLAGS extend_flags; VOLUME_FLAGS volume_flags = const_cpu_to_le16(0); + int sectors_per_cluster; int nr_sysfiles; int buf_sds_first_size; char *buf_sds; @@ -4639,8 +4644,11 @@ static BOOL mkntfs_create_root_structures(void) * already inserted, so no need to worry about these things. */ bs->bpb.bytes_per_sector = cpu_to_le16(opts.sector_size); - bs->bpb.sectors_per_cluster = (u8)(g_vol->cluster_size / - opts.sector_size); + sectors_per_cluster = g_vol->cluster_size / opts.sector_size; + if (sectors_per_cluster > 128) + bs->bpb.sectors_per_cluster = 257 - ffs(sectors_per_cluster); + else + bs->bpb.sectors_per_cluster = sectors_per_cluster; bs->bpb.media_type = 0xf8; /* hard disk */ bs->bpb.sectors_per_track = cpu_to_le16(opts.sectors_per_track); ntfs_log_debug("sectors per track = %ld (0x%lx)\n", diff --git a/ntfsprogs/ntfscat.c b/ntfsprogs/ntfscat.c index 5b938ab6..b4a0e608 100644 --- a/ntfsprogs/ntfscat.c +++ b/ntfsprogs/ntfscat.c @@ -236,6 +236,7 @@ static int parse_options(int argc, char **argv) optarg); usage(); } + break; case 'q': opts.quiet++; diff --git a/ntfsprogs/ntfsclone.c b/ntfsprogs/ntfsclone.c index 76c7e65c..4fe5449f 100644 --- a/ntfsprogs/ntfsclone.c +++ b/ntfsprogs/ntfsclone.c @@ -3,7 +3,7 @@ * * Copyright (c) 2003-2006 Szabolcs Szakacsits * Copyright (c) 2004-2006 Anton Altaparmakov - * Copyright (c) 2010-2016 Jean-Pierre Andre + * Copyright (c) 2010-2018 Jean-Pierre Andre * Special image format support copyright (c) 2004 Per Olofsson * * Clone NTFS data and/or metadata to a sparse file, image, device or stdout. @@ -71,6 +71,7 @@ */ #define NTFS_DO_NOT_CHECK_ENDIANS +#include "param.h" #include "debug.h" #include "types.h" #include "support.h" @@ -270,7 +271,6 @@ static int compare_bitmaps(struct bitmap *a, BOOL copy); #define LAST_METADATA_INODE 11 -#define NTFS_MAX_CLUSTER_SIZE 65536 #define NTFS_SECTOR_SIZE 512 #define rounded_up_division(a, b) (((a) + (b - 1)) / (b)) @@ -393,7 +393,7 @@ static void version(void) "Efficiently clone, image, restore or rescue an NTFS Volume.\n\n" "Copyright (c) 2003-2006 Szabolcs Szakacsits\n" "Copyright (c) 2004-2006 Anton Altaparmakov\n" - "Copyright (c) 2010-2016 Jean-Pierre Andre\n\n"); + "Copyright (c) 2010-2018 Jean-Pierre Andre\n\n"); fprintf(stderr, "%s\n%s%s", ntfs_gpl, ntfs_bugs, ntfs_home); exit(0); } @@ -465,6 +465,7 @@ static void parse_options(int argc, char **argv) break; case 'O': opt.overwrite++; + /* FALLTHRU */ case 'o': if (opt.output) usage(1); @@ -756,7 +757,7 @@ static void read_rescue(void *fd, char *buff, u32 csize, u32 bytes_per_sector, static void copy_cluster(int rescue, u64 rescue_lcn, u64 lcn) { - char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */ + char *buff; /* vol is NULL if opt.restore_image is set */ s32 csize = le32_to_cpu(image_hdr.cluster_size); BOOL backup_bootsector; @@ -773,6 +774,9 @@ static void copy_cluster(int rescue, u64 rescue_lcn, u64 lcn) } rescue_pos = (off_t)(rescue_lcn * csize); + buff = (char*)ntfs_malloc(csize); + if (!buff) + err_exit("Not enough memory"); /* possible partial cluster holding the backup boot sector */ backup_bootsector = (lcn + 1)*csize >= full_device_size; @@ -858,6 +862,7 @@ static void copy_cluster(int rescue, u64 rescue_lcn, u64 lcn) perr_printf("Write failed"); #endif } + free(buff); } static s64 lseek_out(int fd, s64 pos, int mode) @@ -995,7 +1000,11 @@ static void write_empty_clusters(s32 csize, s64 count, struct progress_bar *progress, u64 *p_counter) { s64 i; - char buff[NTFS_MAX_CLUSTER_SIZE]; + char *buff; + + buff = (char*)ntfs_malloc(csize); + if (!buff) + err_exit("Not enough memory"); memset(buff, 0, csize); @@ -1004,6 +1013,7 @@ static void write_empty_clusters(s32 csize, s64 count, perr_exit("write_all"); progress_update(progress, ++(*p_counter)); } + free(buff); } static void restore_image(void) @@ -1492,11 +1502,12 @@ static void write_set(char *buff, u32 csize, s64 *current_lcn, static void copy_wipe_mft(ntfs_walk_clusters_ctx *image, runlist *rl) { - char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */ + char *buff; void *fd; s64 mft_no; u32 mft_record_size; u32 csize; + u32 buff_size; u32 bytes_per_sector; u32 records_per_set; u32 clusters_per_set; @@ -1514,14 +1525,21 @@ static void copy_wipe_mft(ntfs_walk_clusters_ctx *image, runlist *rl) /* * Depending on the sizes, there may be several records * per cluster, or several clusters per record. + * Anyway, records are read and rescued by full clusters. */ if (csize >= mft_record_size) { records_per_set = csize/mft_record_size; clusters_per_set = 1; + buff_size = csize; } else { clusters_per_set = mft_record_size/csize; records_per_set = 1; + buff_size = mft_record_size; } + buff = (char*)ntfs_malloc(buff_size); + if (!buff) + err_exit("Not enough memory"); + mft_no = 0; ri = rj = 0; wi = wj = 0; @@ -1554,6 +1572,7 @@ static void copy_wipe_mft(ntfs_walk_clusters_ctx *image, runlist *rl) } } image->current_lcn = current_lcn; + free(buff); } /* @@ -1566,10 +1585,11 @@ static void copy_wipe_mft(ntfs_walk_clusters_ctx *image, runlist *rl) static void copy_wipe_i30(ntfs_walk_clusters_ctx *image, runlist *rl) { - char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */ + char *buff; void *fd; u32 indx_record_size; u32 csize; + u32 buff_size; u32 bytes_per_sector; u32 records_per_set; u32 clusters_per_set; @@ -1586,15 +1606,22 @@ static void copy_wipe_i30(ntfs_walk_clusters_ctx *image, runlist *rl) /* * Depending on the sizes, there may be several records * per cluster, or several clusters per record. + * Anyway, records are read and rescued by full clusters. */ indx_record_size = image->ni->vol->indx_record_size; if (csize >= indx_record_size) { records_per_set = csize/indx_record_size; clusters_per_set = 1; + buff_size = csize; } else { clusters_per_set = indx_record_size/csize; records_per_set = 1; + buff_size = indx_record_size; } + buff = (char*)ntfs_malloc(buff_size); + if (!buff) + err_exit("Not enough memory"); + ri = rj = 0; wi = wj = 0; if (rl[ri].length) @@ -1627,6 +1654,7 @@ static void copy_wipe_i30(ntfs_walk_clusters_ctx *image, runlist *rl) } } image->current_lcn = current_lcn; + free(buff); } static void dump_clusters(ntfs_walk_clusters_ctx *image, runlist *rl) @@ -1711,10 +1739,17 @@ static void walk_runs(struct ntfs_walk_cluster *walk) for (j = 0; j < lcn_length; j++) { u64 k = (u64)lcn + j; - if (ntfs_bit_get_and_set(lcn_bitmap.bm, k, 1)) - err_exit("Cluster %llu referenced twice!\n" - "You didn't shutdown your Windows " - "properly?\n", (unsigned long long)k); + if (ntfs_bit_get_and_set(lcn_bitmap.bm, k, 1)) { + if (opt.ignore_fs_check) + Printf("Cluster %llu is referenced" + " twice!\n", + (unsigned long long)k); + else + err_exit("Cluster %llu referenced" + " twice!\nYou didn't shutdown" + " your Windows properly?\n", + (unsigned long long)k); + } } if (!opt.metadata_image) diff --git a/ntfsprogs/ntfscluster.8.in b/ntfsprogs/ntfscluster.8.in index 9186a648..b0052df1 100644 --- a/ntfsprogs/ntfscluster.8.in +++ b/ntfsprogs/ntfscluster.8.in @@ -69,7 +69,7 @@ This option is not yet implemented. .TP \fB\-q\fR, \fB\-\-quiet\fR Reduce the amount of output to a minimum. Naturally, it doesn't make sense to -combine this option with +combine this option with \fB\-\-verbose\fR .TP \fB\-s\fR, \fB\-\-sector\fR RANGE Any files whose data is in this range of sectors will be displayed. diff --git a/ntfsprogs/ntfscp.8.in b/ntfsprogs/ntfscp.8.in index bf5127ba..67973331 100644 --- a/ntfsprogs/ntfscp.8.in +++ b/ntfsprogs/ntfscp.8.in @@ -59,6 +59,10 @@ Show a list of options with a brief description of each one. \fB\-q\fR, \fB\-\-quiet\fR Suppress some debug/warning/error messages. .TP +\fB\-t\fR, \fB\-\-timestamp\fR +Copy the modification time of source_file to destination. This is +not compatible with \fB\-\-attr\-name\fR and \fB\-\-attribute\fR. +.TP \fB\-V\fR, \fB\-\-version\fR Show the version number, copyright and license .BR ntfscp . diff --git a/ntfsprogs/ntfscp.c b/ntfsprogs/ntfscp.c index 5f4f8dda..12fdb39d 100644 --- a/ntfsprogs/ntfscp.c +++ b/ntfsprogs/ntfscp.c @@ -4,7 +4,7 @@ * Copyright (c) 2004-2007 Yura Pakhuchiy * Copyright (c) 2005 Anton Altaparmakov * Copyright (c) 2006 Hil Liao - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-2019 Jean-Pierre Andre * * This utility will copy file to an NTFS volume. * @@ -58,6 +58,7 @@ #include "debug.h" /* #include "version.h" */ #include "logging.h" +#include "ntfstime.h" #include "misc.h" struct options { @@ -69,6 +70,7 @@ struct options { int quiet; /* Less output */ int verbose; /* Extra output */ int minfragments; /* Do minimal fragmentation */ + int timestamp; /* Copy the modification time */ int noaction; /* Do not write to disk */ ATTR_TYPES attribute; /* Write to this attribute. */ int inode; /* Treat dest_file as inode number. */ @@ -129,6 +131,7 @@ static void usage(void) " -N, --attr-name NAME Write to attribute with this name\n" " -n, --no-action Do not write to disk\n" " -q, --quiet Less output\n" + " -t, --timestamp Copy the modification time\n" " -V, --version Version information\n" " -v, --verbose More output\n\n", EXEC_NAME); @@ -146,7 +149,7 @@ static void usage(void) */ static int parse_options(int argc, char **argv) { - static const char *sopt = "-a:ifh?mN:no:qVv"; + static const char *sopt = "-a:ifh?mN:no:qtVv"; static const struct option lopt[] = { { "attribute", required_argument, NULL, 'a' }, { "inode", no_argument, NULL, 'i' }, @@ -156,6 +159,7 @@ static int parse_options(int argc, char **argv) { "attr-name", required_argument, NULL, 'N' }, { "no-action", no_argument, NULL, 'n' }, { "quiet", no_argument, NULL, 'q' }, + { "timestamp", no_argument, NULL, 't' }, { "version", no_argument, NULL, 'V' }, { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } @@ -175,6 +179,7 @@ static int parse_options(int argc, char **argv) opts.attr_name = NULL; opts.inode = 0; opts.attribute = AT_DATA; + opts.timestamp = 0; opterr = 0; /* We'll handle the errors, thank you. */ @@ -235,6 +240,9 @@ static int parse_options(int argc, char **argv) opts.quiet++; ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET); break; + case 't': + opts.timestamp++; + break; case 'V': ver++; break; @@ -284,6 +292,12 @@ static int parse_options(int argc, char **argv) "at the same time.\n"); err++; } + if (opts.timestamp + && (opts.attr_name || !le32_eq(opts.attribute, AT_DATA))) { + ntfs_log_error("Setting --timestamp is only possible" + " with unname data attribute.\n"); + err++; + } } if (ver) @@ -822,6 +836,7 @@ static ntfs_inode *ntfs_new_file(ntfs_inode *dir_ni, int main(int argc, char *argv[]) { FILE *in; + struct stat st; ntfs_volume *vol; ntfs_inode *out; ntfs_attr *na; @@ -1136,6 +1151,16 @@ int main(int argc, char *argv[]) free(buf); close_attr: ntfs_attr_close(na); + if (opts.timestamp) { + if (!fstat(fileno(in),&st)) { + s64 change_time = st.st_mtime*10000000LL + + NTFS_TIME_OFFSET; + out->last_data_change_time = cpu_to_le64(change_time); + ntfs_inode_update_times(out, 0); + } else { + ntfs_log_error("Failed to get the time stamp.\n"); + } + } close_dst: while (ntfs_inode_close(out) && !opts.noaction) { if (errno != EBUSY) { diff --git a/ntfsprogs/ntfsdecrypt.c b/ntfsprogs/ntfsdecrypt.c index f9e215a7..08bdfacd 100644 --- a/ntfsprogs/ntfsdecrypt.c +++ b/ntfsprogs/ntfsdecrypt.c @@ -567,6 +567,7 @@ check_again: switch (err) { case GNUTLS_BAG_PKCS8_KEY: flags = GNUTLS_PKCS_PLAIN; + /* FALLTHRU */ case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: err = gnutls_pkcs12_bag_get_data(bag, 0, &dkey); if (err < 0) { @@ -1491,12 +1492,15 @@ static int ntfs_feed_encrypt(ntfs_inode *inode, ntfs_fek *fek) default : *b++ = val; val >>= 8; + /* FALLTHRU */ case 3 : *b++ = val; val >>= 8; + /* FALLTHRU */ case 2 : *b++ = val; val >>= 8; + /* FALLTHRU */ case 1 : *b++ = val; val >>= 8; diff --git a/ntfsprogs/ntfsfallocate.c b/ntfsprogs/ntfsfallocate.c index 179d867c..ca2503aa 100644 --- a/ntfsprogs/ntfsfallocate.c +++ b/ntfsprogs/ntfsfallocate.c @@ -1,7 +1,7 @@ /** * ntfsfallocate * - * Copyright (c) 2013-2014 Jean-Pierre Andre + * Copyright (c) 2013-2020 Jean-Pierre Andre * * This utility will allocate clusters to a specified attribute belonging * to a specified file or directory, to a specified length. @@ -214,10 +214,15 @@ static s64 option_value(const char *arg) count = 0; switch (*s++) { case 'E' : count++; + /* FALLTHRU */ case 'P' : count++; + /* FALLTHRU */ case 'T' : count++; + /* FALLTHRU */ case 'G' : count++; + /* FALLTHRU */ case 'M' : count++; + /* FALLTHRU */ case 'K' : count++; switch (*s++) { case 'i' : @@ -770,10 +775,14 @@ static int ntfs_fallocate(ntfs_inode *ni, s64 alloc_offs, s64 alloc_len) /* Get and save the initial allocations */ allocated_size = na->allocated_size; data_size = ni->data_size; - err = ntfs_attr_map_whole_runlist(na); + if (na->rl) + err = ntfs_attr_map_whole_runlist(na); if (!err) { - oldrl = ntfs_save_rl(na->rl); - if (oldrl) { + if (na->rl) + oldrl = ntfs_save_rl(na->rl); + else + oldrl = (runlist_element*)NULL; + if (!na->rl || oldrl) { err = ntfs_full_allocation(na, ctx, alloc_offs, alloc_len); if (err) { diff --git a/ntfsprogs/ntfsfix.c b/ntfsprogs/ntfsfix.c index e28ca384..b23a8e16 100644 --- a/ntfsprogs/ntfsfix.c +++ b/ntfsprogs/ntfsfix.c @@ -506,6 +506,11 @@ static int fix_mftmirr(ntfs_volume *vol) ntfs_log_info("Comparing $MFTMirr to $MFT... "); done = FALSE; + /* + * Since 2017, Windows 10 does not mirror to full $MFTMirr when + * using big clusters, and some records may be found different. + * Nevertheless chkdsk.exe mirrors it fully, so we do similarly. + */ for (i = 0; i < vol->mftmirr_size; ++i) { MFT_RECORD *mrec, *mrec2; const char *ESTR[12] = { "$MFT", "$MFTMirr", "$LogFile", diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index 236866a2..44f49a52 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -8,7 +8,7 @@ * Copyright (c) 2004-2005 Yuval Fledel * Copyright (c) 2004-2007 Yura Pakhuchiy * Copyright (c) 2005 Cristian Klein - * Copyright (c) 2011-2015 Jean-Pierre Andre + * Copyright (c) 2011-2020 Jean-Pierre Andre * * This utility will dump a file's attributes. * @@ -119,7 +119,7 @@ static void version(void) printf(" 2003 Leonard Norrgård\n"); printf(" 2004-2005 Yuval Fledel\n"); printf(" 2004-2007 Yura Pakhuchiy\n"); - printf(" 2011-2014 Jean-Pierre Andre\n"); + printf(" 2011-2018 Jean-Pierre Andre\n"); printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); } @@ -331,7 +331,7 @@ static char *ntfsinfo_time_to_str(const sle64 sle_ntfs_clock) "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } ; static const char *wdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } ; - static char str[30]; + static char str[50]; long long stamp; u32 days; u32 seconds; @@ -371,7 +371,7 @@ static char *ntfsinfo_time_to_str(const sle64 sle_ntfs_clock) mon = days/31 + 1; days -= 31*(mon - 1) - 1; } - sprintf(str,"%3s %3s %2u %02u:%02u:%02u %4u UTC\n", + snprintf(str, sizeof(str), "%3s %3s %2u %02u:%02u:%02u %4u UTC\n", wdays[wday], months[mon-1],(unsigned int)days, (unsigned int)(seconds/3600), @@ -411,20 +411,49 @@ static char *ntfs_attr_get_name_mbs(ATTR_RECORD *attr) static const char *reparse_type_name(le32 tag) { const char *name; + le32 seltag; + seltag = le32_and(tag, IO_REPARSE_PLUGIN_SELECT); do { - if (le32_eq(tag, IO_REPARSE_TAG_MOUNT_POINT)) { + if (le32_eq(seltag, IO_REPARSE_TAG_MOUNT_POINT)) { name = " (mount point)"; break; - } else if (le32_eq(tag, IO_REPARSE_TAG_SYMLINK)) { + } else if (le32_eq(seltag, IO_REPARSE_TAG_SYMLINK)) { name = " (symlink)"; break; - } else if (le32_eq(tag, IO_REPARSE_TAG_WOF)) { + } else if (le32_eq(seltag, IO_REPARSE_TAG_WOF)) { name = " (Wof compressed)"; break; - } else if (le32_eq(tag, IO_REPARSE_TAG_DEDUP)) { + } else if (le32_eq(seltag, IO_REPARSE_TAG_DEDUP)) { name = " (deduplicated)"; break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_WCI)) { + name = " (Windows container)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_CLOUD)) { + name = " (Cloud)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_NFS)) { + name = " (NFS symlink)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_LX_SYMLINK)) { + name = " (Linux symlink)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_LX_FIFO)) { + name = " (Linux fifo)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_LX_CHR)) { + name = " (Linux character device)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_LX_BLK)) { + name = " (Linux block device)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_AF_UNIX)) { + name = " (Unix socket)"; + break; + } else if (le32_eq(seltag, IO_REPARSE_TAG_APPEXECLINK)) { + name = " (Exec link)"; + break; } else { name = ""; break; @@ -605,6 +634,10 @@ static void ntfs_dump_flags(const char *indent, ATTR_TYPES type, le32 flags) printf(" VIEW_INDEX"); flags = le32_and(flags, le32_not(FILE_ATTR_VIEW_INDEX_PRESENT)); } + if (!le32_andz(flags, FILE_ATTRIBUTE_RECALL_ON_OPEN)) { + printf(" RECALL_ON_OPEN"); + flags = le32_and(flags, le32_not(FILE_ATTRIBUTE_RECALL_ON_OPEN)); + } if (!le32_cmpz(flags)) printf(" UNKNOWN: 0x%08x", (unsigned int)le32_to_cpu(flags)); /* Print all the flags in hex. */ diff --git a/ntfsprogs/ntfsprogs.8.in b/ntfsprogs/ntfsprogs.8.in index f00f5f49..82abf861 100644 --- a/ntfsprogs/ntfsprogs.8.in +++ b/ntfsprogs/ntfsprogs.8.in @@ -31,6 +31,9 @@ available for free and come with full source code. .BR ntfscp (8) \- Copy a file to an NTFS volume. .PP +.BR ntfsfallocate (8) +\- Preallocate space to a file on an NTFS volume +.PP .BR ntfsfix (8) \- Check and fix some common errors, clear the LogFile and make Windows perform a thorough check next time it boots. @@ -47,6 +50,9 @@ perform a thorough check next time it boots. .BR ntfsresize (8) \- Resize NTFS without losing data. .PP +.BR ntfsrecover (8) +\- Recover updates committed by Windows on an NTFS volume. +.PP .BR ntfstruncate (8) \- Truncate a file on an NTFS volume. .PP diff --git a/ntfsprogs/ntfsrecover.c b/ntfsprogs/ntfsrecover.c index 05bd5d1e..5081494c 100644 --- a/ntfsprogs/ntfsrecover.c +++ b/ntfsprogs/ntfsrecover.c @@ -1,7 +1,7 @@ /* * Process log data from an NTFS partition * - * Copyright (c) 2012-2016 Jean-Pierre Andre + * Copyright (c) 2012-2017 Jean-Pierre Andre * * This program examines the Windows log file of an ntfs partition * and plays the committed transactions in order to restore the @@ -43,6 +43,7 @@ */ #define BASEBLKS 4 /* number of special blocks (always shown) */ +#define BASEBLKS2 34 /* number of special blocks when version >= 2.0 */ #define RSTBLKS 2 /* number of restart blocks */ #define BUFFERCNT 64 /* number of block buffers - a power of 2 */ #define NTFSBLKLTH 512 /* usa block size */ @@ -122,6 +123,7 @@ u32 clustersz = 0; int clusterbits; u32 blocksz; int blockbits; +int log_major; u16 bytespersect; u64 mftlcn; u32 mftrecsz; @@ -136,6 +138,7 @@ u64 committed_lsn; u64 synced_lsn; u64 latest_lsn; u64 restart_lsn; +u64 offset_mask; /* block number in an lsn */ unsigned long firstblk; /* first block to dump (option -r) */ unsigned long lastblk; /* last block to dump (option -r) */ u64 firstlcn; /* first block to dump (option -c) */ @@ -164,6 +167,7 @@ unsigned int playedactions; // change the name unsigned int redocount; unsigned int undocount; struct BUFFER *buffer_table[BASEBLKS + BUFFERCNT]; +unsigned int redirect[BASEBLKS2]; static const le16 SDS[4] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'), @@ -319,18 +323,22 @@ static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num) { struct BUFFER *buffer; BOOL got; + int k; + unsigned int rnum; /* * The first four blocks are stored apart, to make * sure pages 2 and 3 and the page which is logically * before them can be accessed at the same time. + * (Only two blocks are stored apart if version >= 2.0) * Also, block 0 is smaller because it has to be read * before the block size is known. * Note : the last block is supposed to have an odd - * number, and cannot be overwritten by block 4 which - * follows logically. + * number, and cannot be overwritten by block 4 (or 34 + * if version >= 2.0) which follows logically. */ - if (num < BASEBLKS) + if ((num < RSTBLKS) + || ((log_major < 2) && (num < BASEBLKS))) buffer = buffer_table[num + BUFFERCNT]; else buffer = buffer_table[num & (BUFFERCNT - 1)]; @@ -342,20 +350,27 @@ static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num) buffer = (struct BUFFER*) malloc(sizeof(struct BUFFER) + blocksz); buffer->size = blocksz; - buffer->num = num + 1; /* forced to being read */ + buffer->rnum = num + 1; /* forced to being read */ buffer->safe = FALSE; if (num < BASEBLKS) buffer_table[num + BUFFERCNT] = buffer; else buffer_table[num & (BUFFERCNT - 1)] = buffer; } - if (buffer && (buffer->num != num)) { + rnum = num; + if (log_major >= 2) { + for (k=RSTBLKS; krnum != rnum)) { buffer->num = num; + buffer->rnum = rnum; if (ctx->vol) - got = (ntfs_attr_pread(log_na,(u64)num << blockbits, + got = (ntfs_attr_pread(log_na,(u64)rnum << blockbits, blocksz, buffer->block.data) == blocksz); else - got = !fseek(ctx->file, loclogblk(ctx, num), 0) + got = !fseek(ctx->file, loclogblk(ctx, rnum), 0) && (fread(buffer->block.data, blocksz, 1, ctx->file) == 1); if (got) { @@ -365,7 +380,7 @@ static const struct BUFFER *read_buffer(CONTEXT *ctx, unsigned int num) buffer->safe = !replaceusa(buffer, blocksz); } else { buffer->safe = FALSE; - fprintf(stderr,"** Could not read block %d\n", num); + fprintf(stderr,"** Could not read block %d\n", rnum); } } return (buffer && buffer->safe ? buffer : (const struct BUFFER*)NULL); @@ -1096,22 +1111,30 @@ static const struct BUFFER *findprevious(CONTEXT *ctx, const struct BUFFER *buf) skipped = 0; do { prevmiddle = FALSE; - if (prevblk > BASEBLKS) + if (prevblk > (log_major < 2 ? BASEBLKS : BASEBLKS2)) prevblk--; else - if (prevblk == BASEBLKS) + if (prevblk == (log_major < 2 ? BASEBLKS : BASEBLKS2)) prevblk = (logfilesz >> blockbits) - 1; else { rph = &buf->block.record; - prevblk = (sle64_to_cpu(rph->copy.file_offset) + if (log_major < 2) + prevblk = (sle64_to_cpu( + rph->copy.file_offset) >> blockbits) - 1; + else + prevblk = (sle64_to_cpu( + rph->copy.last_lsn) + & offset_mask) + >> (blockbits - 3); /* * If an initial block leads to block 4, it * can mean the last block or no previous * block at all. Using the last block is safer, * its lsn will indicate whether it is stale. */ - if (prevblk < BASEBLKS) + if (prevblk + < (log_major < 2 ? BASEBLKS : BASEBLKS2)) prevblk = (logfilesz >> blockbits) - 1; } /* No previous block if the log only consists of block 2 or 3 */ @@ -2706,7 +2729,7 @@ static void showrest(const RESTART_PAGE_HEADER *rest) (long)le32_to_cpu(rest->system_page_size)); printf("log_page_size %08lx\n", (long)le32_to_cpu(rest->log_page_size)); - printf("restart_area_offset %04x\n", + printf("restart_area_offset %04x\n", (int)le16_to_cpu(rest->restart_area_offset)); printf("minor_vers %d\n", (int)sle16_to_cpu(rest->minor_ver)); @@ -2876,6 +2899,8 @@ static BOOL dorest(CONTEXT *ctx, unsigned long blk, } } restart_lsn = synced_lsn; + offset_mask = ((u64)1 << (64 - le32_to_cpu(restart.seq_number_bits))) + - (1 << (blockbits - 3)); return (dirty); } @@ -2895,9 +2920,13 @@ static const struct BUFFER *read_restart(CONTEXT *ctx) { const struct BUFFER *buf; BOOL bad; + int blk; int major, minor; bad = FALSE; + for (blk=0; blkvol) { RESTART_PAGE_HEADER *rph; @@ -2961,6 +2990,7 @@ static const struct BUFFER *read_restart(CONTEXT *ctx) major, minor); bad = TRUE; } + log_major = major; if (bad) { buf = (const struct BUFFER*)NULL; } @@ -3343,7 +3373,8 @@ static TRISTATE backoverlap(CONTEXT *ctx, int blk, mblk = blk + 1; while (total < size) { if (mblk >= (logfilesz >> blockbits)) - mblk = BASEBLKS; + mblk = (log_major < 2 ? BASEBLKS + : BASEBLKS2); more = size - total; if (more > nextspace) more = nextspace; @@ -3427,9 +3458,15 @@ static TRISTATE backward_rcrd(CONTEXT *ctx, u32 blk, int skipped, if (optv) { if (optv >= 2) hexdump(data,blocksz); - printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n", - (long)blk,(long)blk, - (long long)loclogblk(ctx, blk)); + if (buf->rnum != blk) + printf("* RCRD for block %ld 0x%lx" + " in block %ld (addr 0x%llx)\n", + (long)blk,(long)blk,(long)buf->rnum, + (long long)loclogblk(ctx, blk)); + else + printf("* RCRD in block %ld 0x%lx (addr 0x%llx)\n", + (long)blk,(long)blk, + (long long)loclogblk(ctx, blk)); } else { if (optt) printf("block %ld\n",(long)blk); @@ -3551,9 +3588,15 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk, u32 stopblk; TRISTATE state; - if (optv) - printf("\n* block %d at 0x%llx\n",(int)blk, + if (optv) { + if ((log_major >= 2) && (buf->rnum != blk)) + printf("\n* block %d for block %d at 0x%llx\n", + (int)buf->rnum,(int)blk, + (long long)loclogblk(ctx, buf->rnum)); + else + printf("\n* block %d at 0x%llx\n",(int)blk, (long long)loclogblk(ctx, blk)); + } ctx->firstaction = (struct ACTION_RECORD*)NULL; ctx->lastaction = (struct ACTION_RECORD*)NULL; nextbuf = (const struct BUFFER*)NULL; @@ -3576,7 +3619,9 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk, skipped = blk - prevblk - 1; else skipped = blk - prevblk - 1 - + (logfilesz >> blockbits) - BASEBLKS; + + (logfilesz >> blockbits) + - (log_major < 2 ? BASEBLKS + : BASEBLKS2); magic = prevbuf->block.record.magic; /* switch (magic) { */ if (le32_eq(magic, magic_RCRD)) { @@ -3599,9 +3644,18 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk, (long long)loclogblk(ctx, blk), (long)prevblk); else - printf("\n* block %ld at 0x%llx\n", - (long)blk, - (long long)loclogblk(ctx, blk)); + if ((log_major >= 2) + && (buf->rnum != blk)) + printf("\n* block %ld for block %ld at 0x%llx\n", + (long)buf->rnum, + (long)blk, + (long long)loclogblk( + ctx,buf->rnum)); + else + printf("\n* block %ld at 0x%llx\n", + (long)blk, + (long long)loclogblk( + ctx, blk)); } state = backward_rcrd(ctx, blk, skipped, buf, prevbuf, nextbuf); @@ -3632,6 +3686,155 @@ static int walkback(CONTEXT *ctx, const struct BUFFER *buf, u32 blk, return (state == T_ERR ? 1 : 0); } +/* + * Find the latest log block + * + * Usually, the latest block is either block 2 or 3 which act as + * temporary block before being copied to target location. + * However under some unknown condition the block are written + * immediately to target location, and we have to scan for the + * latest one. + * Currently this is not checked for logfile version 2.x which + * use a different layout of temporary blocks. + */ + +static const struct BUFFER *find_latest_block(CONTEXT *ctx, u32 baseblk, + const struct BUFFER *basebuf) +{ + le64 offset; + leLSN prevlsn; + leLSN curlsn; + u32 curblk; + u32 prevblk; + const struct BUFFER *prevbuf; + const struct BUFFER *curbuf; + + offset = basebuf->block.record.copy.file_offset; + curbuf = (const struct BUFFER*)NULL; + curlsn = const_cpu_to_le64(0); + prevblk = 0; + curblk = baseblk; + do { + if (curblk < BASEBLKS) { + prevbuf = basebuf; + prevlsn = basebuf->block.record.last_end_lsn; + prevblk = baseblk; + curblk = le64_to_cpu(offset) >> blockbits; + } else { + if (optv) + printf("block %d is more recent than block %d\n", + (int)curblk, (int)prevblk); + prevbuf = curbuf; + prevlsn = curlsn; + prevblk = curblk; + curblk++; + if (curblk >= (logfilesz >> blockbits)) + curblk = (log_major < 2 ? BASEBLKS : BASEBLKS2); + } + curbuf = read_buffer(ctx, curblk); + if (curbuf && (curbuf->block.record.magic == magic_RCRD)) { + curlsn = curbuf->block.record.copy.last_lsn; + } + } while (curbuf + && (curbuf->block.record.magic == magic_RCRD) + && (le64_to_cpu(curlsn) > le64_to_cpu(prevlsn))); + if (optv) + printf("Block %d is the latest one\n",(int)prevblk); + return (prevbuf); +} + +/* + * Determine the sequencing of blocks (when version >= 2.0) + * + * Blocks 2..17 and 18..33 are temporary blocks being filled until + * they are copied to their target locations, so there are three + * possible location for recent blocks. + * + * Returns the latest target block number + */ + +static int block_sequence(CONTEXT *ctx) +{ + const struct BUFFER *buf; + int blk; + int k; + int target_blk; + int latest_blk; + s64 final_lsn; + s64 last_lsn; + s64 last_lsn12; + s64 last_lsn1, last_lsn2; + + final_lsn = 0; + for (blk=RSTBLKS; 2*blk<(RSTBLKS+BASEBLKS2); blk++) { + /* First temporary block */ + last_lsn1 = 0; + buf = read_buffer(ctx, blk); + if (buf && (buf->block.record.magic == magic_RCRD)) { + last_lsn1 = le64_to_cpu( + buf->block.record.copy.last_lsn); + if (!final_lsn + || ((s64)(last_lsn1 - final_lsn) > 0)) + final_lsn = last_lsn1; + } + /* Second temporary block */ + buf = read_buffer(ctx, blk + (BASEBLKS2 - RSTBLKS)/2); + last_lsn2 = 0; + if (buf && (buf->block.record.magic == magic_RCRD)) { + last_lsn2 = le64_to_cpu( + buf->block.record.copy.last_lsn); + if (!final_lsn + || ((s64)(last_lsn2 - final_lsn) > 0)) + final_lsn = last_lsn2; + } + /* the latest last_lsn defines the target block */ + last_lsn12 = 0; + latest_blk = 0; + if (last_lsn1 || last_lsn2) { + if (!last_lsn2 + || ((s64)(last_lsn1 - last_lsn2) > 0)) { + last_lsn12 = last_lsn1; + latest_blk = blk; + } + if (!last_lsn1 + || ((s64)(last_lsn1 - last_lsn2) <= 0)) { + last_lsn12 = last_lsn2; + latest_blk = blk + (BASEBLKS2 - RSTBLKS)/2; + } + } + last_lsn = 0; + target_blk = 0; + if (last_lsn12) { + target_blk = (last_lsn12 & offset_mask) + >> (blockbits - 3); + buf = read_buffer(ctx, target_blk); + if (buf && (buf->block.record.magic == magic_RCRD)) { + last_lsn = le64_to_cpu( + buf->block.record.copy.last_lsn); + if (!final_lsn + || ((s64)(last_lsn - final_lsn) > 0)) + final_lsn = last_lsn; + } + } + /* redirect to the latest block */ + if (latest_blk + && (!last_lsn || ((s64)(last_lsn - last_lsn12) < 0))) + redirect[latest_blk] = target_blk; + } + if (optv) { + printf("\n Blocks redirected :\n"); + for (k=RSTBLKS; k> (blockbits - 3); + if (optv > 1) + printf("final lsn %llx in blk %d\n",(long long)final_lsn,blk); + return (blk); +} + static int walk(CONTEXT *ctx) { const struct BUFFER *buf; @@ -3644,6 +3847,7 @@ static int walk(CONTEXT *ctx) u32 blk; u32 nextblk; u32 prevblk; + u32 finalblk; int err; u16 blkheadsz; u16 pos; @@ -3657,6 +3861,7 @@ static int walk(CONTEXT *ctx) } done = FALSE; dirty = TRUE; + finalblk = 0; err = 0; blk = 0; pos = 0; @@ -3675,7 +3880,8 @@ static int walk(CONTEXT *ctx) while (!done) { /* next block is needed to process the current one */ if ((nextblk >= (logfilesz >> blockbits)) && (optr || optf)) - nextbuf = read_buffer(ctx, BASEBLKS); + nextbuf = read_buffer(ctx, + (log_major < 2 ? BASEBLKS : BASEBLKS2)); else nextbuf = read_buffer(ctx,nextblk); if (nextbuf) { @@ -3741,17 +3947,30 @@ static int walk(CONTEXT *ctx) } blk = nextblk; nextblk++; + + if (!optr && (log_major >= 2) && (nextblk == RSTBLKS)) { + finalblk = block_sequence(ctx); + if (!finalblk) { + done = TRUE; + err = 1; + } + } + if (optr) { /* Only selected range */ - if ((nextblk == BASEBLKS) && (nextblk < firstblk)) + u32 endblk; + + endblk = (log_major < 2 ? BASEBLKS : RSTBLKS); + if ((nextblk == endblk) && (nextblk < firstblk)) nextblk = firstblk; - if ((blk >= BASEBLKS) && (blk > lastblk)) + if ((blk >= endblk) && (blk > lastblk)) done = TRUE; } else if (optf) { /* Full log, forward */ if (blk*blocksz >= logfilesz) done = TRUE; } else - if (optb || optp || optu || opts) { + if (optb || optp || optu || opts + || (log_major >= 2)) { /* Restart blocks only (2 blocks) */ if (blk >= RSTBLKS) done = TRUE; @@ -3782,16 +4001,18 @@ static int walk(CONTEXT *ctx) } if (optv && opts && !dirty) printf("* Volume is clean, nothing to do\n"); - if (optb || optp || optu - || (opts && dirty)) { + if (log_major >= 2) + blk = finalblk; + if (!err + && (optb || optp || optu || (opts && dirty))) { playedactions = 0; ctx->firstaction = (struct ACTION_RECORD*)NULL; ctx->lastaction = (struct ACTION_RECORD*)NULL; - buf = nextbuf; - nextbuf = read_buffer(ctx, blk+1); - startbuf = best_start(buf,nextbuf); - if (startbuf) { - if (startbuf == nextbuf) { + if (log_major < 2) { + buf = nextbuf; + nextbuf = read_buffer(ctx, blk+1); + startbuf = best_start(buf,nextbuf); + if (startbuf && (startbuf == nextbuf)) { /* nextbuf is better, show blk */ if (optv && buf) { printf("* Ignored block %d at 0x%llx\n", @@ -3818,6 +4039,17 @@ static int walk(CONTEXT *ctx) &nextbuf->block.record); } } + if (startbuf && opts) { + buf = startbuf = find_latest_block(ctx, + blk, startbuf); + latest_lsn = le64_to_cpu( + buf->block.record.last_end_lsn); + } + } else { + buf = startbuf = read_buffer(ctx, blk); + nextbuf = (const struct BUFFER*)NULL; + } + if (startbuf) { /* The latest buf may be more recent than restart */ rph = &buf->block.record; if ((s64)(sle64_to_cpu(rph->last_end_lsn) @@ -3874,7 +4106,7 @@ static void version(void) { printf("\n%s v%s (libntfs-3g) - Recover updates committed by Windows" " on an NTFS Volume.\n\n", "ntfsrecover", VERSION); - printf("Copyright (c) 2012-2016 Jean-Pierre Andre\n"); + printf("Copyright (c) 2012-2017 Jean-Pierre Andre\n"); printf("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); } diff --git a/ntfsprogs/ntfsrecover.h b/ntfsprogs/ntfsrecover.h index abc72a0f..adf03e35 100644 --- a/ntfsprogs/ntfsrecover.h +++ b/ntfsprogs/ntfsrecover.h @@ -74,10 +74,12 @@ enum ACTIONS { struct BUFFER { unsigned int num; + unsigned int rnum; unsigned int size; unsigned int headsz; BOOL safe; union { + u64 alignment; RESTART_PAGE_HEADER restart; RECORD_PAGE_HEADER record; char data[1]; diff --git a/ntfsprogs/ntfsresize.8.in b/ntfsprogs/ntfsresize.8.in index 2ee53460..4c21ed2c 100644 --- a/ntfsprogs/ntfsresize.8.in +++ b/ntfsprogs/ntfsresize.8.in @@ -243,10 +243,9 @@ Display help and exit. .SH EXIT CODES The exit code is 0 on success, non\-zero otherwise. .SH KNOWN ISSUES -No reliability problem is known. If you need -help please try the Ntfsresize FAQ first (see below) and if you -don't find your answer then send your question, comment or bug report to -the development team: +No reliability problem is known. +If you find a bug please send an email describing the problem to the +development team at: .br .nh ntfs\-3g\-devel@lists.sf.net @@ -308,14 +307,6 @@ package and is available from: .nh http://www.tuxera.com/community/ .hy -.sp -.B Ntfsresize -related news, example of usage, troubleshooting, statically linked binary and -FAQ (frequently asked questions) are maintained at: -.br -.nh -http://mlf.linux.rulez.org/mlf/ezaz/ntfsresize.html -.hy .SH SEE ALSO .BR fdisk (8), .BR cfdisk (8), diff --git a/ntfsprogs/ntfsresize.c b/ntfsprogs/ntfsresize.c index 3eb895fb..0a6dbe85 100644 --- a/ntfsprogs/ntfsresize.c +++ b/ntfsprogs/ntfsresize.c @@ -5,7 +5,7 @@ * Copyright (c) 2002-2005 Anton Altaparmakov * Copyright (c) 2002-2003 Richard Russon * Copyright (c) 2007 Yura Pakhuchiy - * Copyright (c) 2011-2016 Jean-Pierre Andre + * Copyright (c) 2011-2018 Jean-Pierre Andre * * This utility will resize an NTFS volume without data loss. * @@ -59,6 +59,7 @@ #include #endif +#include "param.h" #include "debug.h" #include "types.h" #include "support.h" @@ -137,6 +138,8 @@ static const char *many_bad_sectors_msg = "* other reason. We suggest to get a replacement disk as soon as possible. *\n" "***************************************************************************\n"; +enum mirror_source { MIRR_OLD, MIRR_NEWMFT, MIRR_MFT }; + static struct { int verbose; int debug; @@ -226,6 +229,7 @@ typedef struct { struct llcn_t last_compressed; struct llcn_t last_lcn; s64 last_unsupp; /* last unsupported cluster */ + enum mirror_source mirr_from; } ntfs_resize_t; /* FIXME: This, lcn_bitmap and pos from find_free_cluster() will make a cluster @@ -243,8 +247,6 @@ static s64 max_free_cluster_range = 0; #define DIRTY_INODE (1) #define DIRTY_ATTRIB (2) -#define NTFS_MAX_CLUSTER_SIZE (65536) - static s64 rounded_up_division(s64 numer, s64 denom) { return (numer + (denom - 1)) / denom; @@ -404,7 +406,7 @@ static void version(void) printf("Copyright (c) 2002-2005 Anton Altaparmakov\n"); printf("Copyright (c) 2002-2003 Richard Russon\n"); printf("Copyright (c) 2007 Yura Pakhuchiy\n"); - printf("Copyright (c) 2011-2016 Jean-Pierre Andre\n"); + printf("Copyright (c) 2011-2018 Jean-Pierre Andre\n"); printf("\n%s\n%s%s", ntfs_gpl, ntfs_bugs, ntfs_home); } @@ -451,8 +453,10 @@ static s64 get_new_volume_size(char *s) switch (*suffix) { case 'G': size *= prefix_kind; + /* FALLTHRU */ case 'M': size *= prefix_kind; + /* FALLTHRU */ case 'k': size *= prefix_kind; break; @@ -1459,10 +1463,13 @@ static int record_mft_in_bitmap(ntfs_resize_t *resize) static void delayed_updates(ntfs_resize_t *resize) { struct DELAYED *delayed; + struct DELAYED *delayed_mft_data; + int nr_extents; if (ntfs_volume_get_free_space(resize->vol)) err_exit("Failed to determine free space\n"); + delayed_mft_data = (struct DELAYED*)NULL; if (resize->delayed_runlists && reload_mft(resize)) err_exit("Failed to reload the MFT for delayed updates\n"); @@ -1476,19 +1483,55 @@ static void delayed_updates(ntfs_resize_t *resize) * So we update MFT data first, and we record the MFT * extents again in the MFT bitmap if they were recorded * in the old location. + * + * However, if we are operating in "no action" mode, the + * MFT records to update are not written to their new location + * and the MFT data runlist has to be updated last in order + * to have the entries read from their old location. + * In this situation the MFT bitmap is never written to + * disk, so the same extents are reallocated repeatedly, + * which is not what would be done in a real resizing. */ + if (opt.ro_flag + && resize->delayed_runlists + && (resize->delayed_runlists->mref == FILE_MFT) + && le32_eq(resize->delayed_runlists->type, AT_DATA)) { + /* Update the MFT data runlist later */ + delayed_mft_data = resize->delayed_runlists; + resize->delayed_runlists = resize->delayed_runlists->next; + } + while (resize->delayed_runlists) { delayed = resize->delayed_runlists; expand_attribute_runlist(resize->vol, delayed); - if ((delayed->mref == FILE_MFT) && le32_eq(delayed->type, AT_BITMAP)) - record_mft_in_bitmap(resize); + if (delayed->mref == FILE_MFT) { + if (le32_eq(delayed->type, AT_BITMAP)) + record_mft_in_bitmap(resize); + if (le32_eq(delayed->type, AT_DATA)) + resize->mirr_from = MIRR_MFT; + } resize->delayed_runlists = resize->delayed_runlists->next; if (delayed->attr_name) free(delayed->attr_name); free(delayed->head_rl); free(delayed); } + if (opt.ro_flag && delayed_mft_data) { + /* in "no action" mode, check updating the MFT runlist now */ + expand_attribute_runlist(resize->vol, delayed_mft_data); + resize->mirr_from = MIRR_MFT; + if (delayed_mft_data->attr_name) + free(delayed_mft_data->attr_name); + free(delayed_mft_data->head_rl); + free(delayed_mft_data); + } + /* Beware of MFT fragmentation when the target size is too small */ + nr_extents = resize->vol->mft_ni->nr_extents; + if (nr_extents > 2) { + printf("WARNING: The MFT is now severely fragmented" + " (%d extents)\n", nr_extents); + } } /* @@ -1849,9 +1892,13 @@ static void lseek_to_cluster(ntfs_volume *vol, s64 lcn) static void copy_clusters(ntfs_resize_t *resize, s64 dest, s64 src, s64 len) { s64 i; - char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */ + char *buff; ntfs_volume *vol = resize->vol; + buff = (char*)ntfs_malloc(vol->cluster_size); + if (!buff) + perr_exit("ntfs_malloc"); + for (i = 0; i < len; i++) { lseek_to_cluster(vol, src + i); @@ -1875,6 +1922,7 @@ static void copy_clusters(ntfs_resize_t *resize, s64 dest, s64 src, s64 len) resize->relocations++; progress_update(&resize->progress, resize->relocations); } + free(buff); } static void relocate_clusters(ntfs_resize_t *r, runlist *dest_rl, s64 src_lcn) @@ -2262,6 +2310,7 @@ static void relocate_inodes(ntfs_resize_t *resize) err_exit("Could not allocate 16 records in" " the first MFT chunk\n"); } + resize->mirr_from = MIRR_NEWMFT; } for (mref = 0; mref < (MFT_REF)nr_mft_records; mref++) @@ -2716,19 +2765,34 @@ static void update_bootsector(ntfs_resize_t *r) if (vol->dev->d_ops->read(vol->dev, bs, bs_size) == -1) perr_exit("read() error"); - bs->number_of_sectors = cpu_to_sle64(r->new_volume_size * - bs->bpb.sectors_per_cluster); + if (bs->bpb.sectors_per_cluster > 128) + bs->number_of_sectors = cpu_to_sle64(r->new_volume_size + << (256 - bs->bpb.sectors_per_cluster)); + else + bs->number_of_sectors = cpu_to_sle64(r->new_volume_size * + bs->bpb.sectors_per_cluster); - if (r->mftmir_old) { + if (r->mftmir_old || (r->mirr_from == MIRR_MFT)) { r->progress.flags |= NTFS_PROGBAR_SUPPRESS; /* Be sure the MFTMirr holds the updated MFT runlist */ - if (r->new_mft_start) + switch (r->mirr_from) { + case MIRR_MFT : + /* The late updates of MFT have not been synced */ + ntfs_inode_sync(vol->mft_ni); + copy_clusters(r, r->mftmir_rl.lcn, + vol->mft_na->rl->lcn, r->mftmir_rl.length); + break; + case MIRR_NEWMFT : copy_clusters(r, r->mftmir_rl.lcn, r->new_mft_start->lcn, r->mftmir_rl.length); - else + break; + default : copy_clusters(r, r->mftmir_rl.lcn, r->mftmir_old, r->mftmir_rl.length); - bs->mftmirr_lcn = cpu_to_sle64(r->mftmir_rl.lcn); + break; + } + if (r->mftmir_old) + bs->mftmirr_lcn = cpu_to_sle64(r->mftmir_rl.lcn); r->progress.flags &= ~NTFS_PROGBAR_SUPPRESS; } /* Set the start of the relocated MFT */ @@ -3905,6 +3969,7 @@ static int update_runlist(expand_t *expand, s64 inum, ctx.mrec = mrec; resize.mref = inum; resize.delayed_runlists = expand->delayed_runlists; + resize.mirr_from = MIRR_OLD; must_delay = 1; replace_later(&resize,rl,head_rl); expand->delayed_runlists = resize.delayed_runlists; @@ -4578,6 +4643,7 @@ int main(int argc, char **argv) resize.inuse = fsck.inuse; resize.lcn_bitmap = fsck.lcn_bitmap; + resize.mirr_from = MIRR_OLD; set_resize_constraints(&resize); set_disk_usage_constraint(&resize); diff --git a/ntfsprogs/ntfsundelete.c b/ntfsprogs/ntfsundelete.c index 2e582b6d..a30f3572 100644 --- a/ntfsprogs/ntfsundelete.c +++ b/ntfsprogs/ntfsundelete.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Holger Ohmacht * Copyright (c) 2005 Anton Altaparmakov * Copyright (c) 2007 Yura Pakhuchiy - * Copyright (c) 2013-2014 Jean-Pierre Andre + * Copyright (c) 2013-2018 Jean-Pierre Andre * * This utility will recover deleted files from an NTFS volume. * @@ -392,7 +392,7 @@ static void version(void) "Copyright (c) 2004-2005 Holger Ohmacht\n" "Copyright (c) 2005 Anton Altaparmakov\n" "Copyright (c) 2007 Yura Pakhuchiy\n" - "Copyright (c) 2013-2014 Jean-Pierre Andre\n"); + "Copyright (c) 2013-2018 Jean-Pierre Andre\n"); ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); } @@ -567,10 +567,15 @@ static int parse_time(const char *value, time_t *since) switch (suffix[0]) { case 'y': case 'Y': result *= 12; + /* FALLTHRU */ case 'm': case 'M': result *= 4; + /* FALLTHRU */ case 'w': case 'W': result *= 7; + /* FALLTHRU */ case 'd': case 'D': result *= 24; + /* FALLTHRU */ case 'h': case 'H': result *= 3600; + /* FALLTHRU */ case 0: break; @@ -1835,19 +1840,49 @@ static unsigned int write_data(int fd, const char *buffer, static int create_pathname(const char *dir, const char *name, const char *stream, char *buffer, int bufsize) { + struct stat st; + int s; + int len; + int suffix; + if (!name) name = UNKNOWN; - if (dir) + if (dir) { +#ifdef HAVE_WINDOWS_H if (stream) - snprintf(buffer, bufsize, "%s/%s:%s", dir, name, stream); + snprintf(buffer, bufsize, "%s\\%s:%s", dir, name, + stream); + else + snprintf(buffer, bufsize, "%s\\%s", dir, name); +#else + if (stream) + snprintf(buffer, bufsize, "%s/%s:%s", dir, name, + stream); else snprintf(buffer, bufsize, "%s/%s", dir, name); - else +#endif + } else if (stream) snprintf(buffer, bufsize, "%s:%s", name, stream); else snprintf(buffer, bufsize, "%s", name); + len = strlen(buffer); + suffix = 0; +#ifdef HAVE_WINDOWS_H + s = stat(buffer, &st); +#else + s = lstat(buffer, &st); +#endif + while (!s && (suffix < 999)) { + suffix++; + snprintf(&buffer[len], bufsize - len, ".%d", suffix); +#ifdef HAVE_WINDOWS_H + s = stat(buffer, &st); +#else + s = lstat(buffer, &st); +#endif + } return strlen(buffer); } @@ -2012,7 +2047,8 @@ static int undelete_file(ntfs_volume *vol, long long inode) if (d->resident) { fd = open_file(pathname); if (fd < 0) { - ntfs_log_perror("Couldn't create file"); + ntfs_log_perror("Couldn't create file %s", + pathname); goto free; } @@ -2041,7 +2077,8 @@ static int undelete_file(ntfs_volume *vol, long long inode) fd = open_file(pathname); if (fd < 0) { - ntfs_log_perror("Couldn't create output file"); + ntfs_log_perror("Couldn't create file %s", + pathname); goto free; } @@ -2151,9 +2188,11 @@ static int undelete_file(ntfs_volume *vol, long long inode) } set_date(pathname, file->date); if (d->name) - ntfs_log_quiet("Undeleted '%s:%s' successfully.\n", file->pref_name, d->name); + ntfs_log_quiet("Undeleted '%s:%s' successfully to %s.\n", + file->pref_name, d->name, pathname); else - ntfs_log_quiet("Undeleted '%s' successfully.\n", file->pref_name); + ntfs_log_quiet("Undeleted '%s' successfully to %s.\n", + file->pref_name, pathname); } result = 1; free: @@ -2348,7 +2387,7 @@ static int copy_mft(ntfs_volume *vol, long long mft_begin, long long mft_end) create_pathname(opts.dest, name, NULL, pathname, sizeof(pathname)); fd = open_file(pathname); if (fd < 0) { - ntfs_log_perror("Couldn't open output file '%s'", name); + ntfs_log_perror("Couldn't create output file '%s'", name); goto attr; } @@ -2376,6 +2415,7 @@ static int copy_mft(ntfs_volume *vol, long long mft_begin, long long mft_end) } ntfs_log_verbose("Read %lld MFT Records\n", mft_end - mft_begin + 1); + ntfs_log_quiet("MFT extracted to file %s\n", pathname); result = 0; close: close(fd); diff --git a/ntfsprogs/ntfswipe.c b/ntfsprogs/ntfswipe.c index 458cba7d..9478c5b7 100644 --- a/ntfsprogs/ntfswipe.c +++ b/ntfsprogs/ntfswipe.c @@ -314,6 +314,7 @@ static int parse_options(int argc, char *argv[]) case 'i': opts.info++; /* and fall through */ + /* FALLTHRU */ case 'a': opts.directory++; opts.logfile++; diff --git a/ntfsprogs/playlog.c b/ntfsprogs/playlog.c index 1d824836..99cc873c 100644 --- a/ntfsprogs/playlog.c +++ b/ntfsprogs/playlog.c @@ -1,7 +1,7 @@ /* * Redo or undo a list of logged actions * - * Copyright (c) 2014-2016 Jean-Pierre Andre + * Copyright (c) 2014-2017 Jean-Pierre Andre * */ @@ -229,7 +229,7 @@ static int sanity_indx_list(const char *buffer, u32 k, u32 end) err = 0; done = FALSE; - while ((k <= end) && !done) { + while ((k <= end) && !done && !err) { lth = getle16(buffer,k+8); if (optv > 1) /* Usual indexes can be determined from size */ @@ -270,9 +270,20 @@ static int sanity_indx_list(const char *buffer, u32 k, u32 end) (long long)getle64(buffer,k), (int)lth, (int)getle16(buffer,k+12),(int)k); + if ((lth < 80) || (lth & 7)) { + printf("** Invalid index record" + " length %d\n",lth); + err = 1; + } } done = !le16_andz(feedle16(buffer,k+12), INDEX_ENTRY_END) || !lth; - k += lth; + if (lth & 7) { + if (optv <= 1) /* Do not repeat the warning */ + printf("** Invalid index record length %d\n", + lth); + err = 1; + } else + k += lth; } if (k != end) { printf("** Bad index record length %ld (computed %ld)\n", @@ -795,7 +806,9 @@ static int adjust_high_vcn(ntfs_volume *vol, ATTR_RECORD *attr) rl = ntfs_mapping_pairs_decompress(vol, attr, (runlist_element*)NULL); if (rl) { xrl = rl; - while (xrl->length) + if (xrl->length) + xrl++; + while ((xrl->length) && (xrl->lcn != LCN_RL_NOT_MAPPED)) xrl++; high_vcn = xrl->vcn - 1; attr->highest_vcn = cpu_to_sle64(high_vcn); @@ -2117,7 +2130,7 @@ static int redo_delete_file(ntfs_volume *vol, record = (MFT_RECORD*)buffer; if ((target + length) <= mftrecsz) { /* write a void mft entry (needed ?) */ - changed = memcmp(buffer + target, data, length) + changed = (length && memcmp(buffer + target, data, length)) || !le16_andz(record->flags, MFT_RECORD_IN_USE); err = 0; if (changed) { @@ -2157,7 +2170,6 @@ static int redo_delete_index(ntfs_volume *vol, data = ((const char*)&action->record) + get_undo_offset(&action->record); length = le16_to_cpu(action->record.undo_length); -// TODO merge with undo_add_index ? target = le16_to_cpu(action->record.record_offset) + le16_to_cpu(action->record.attribute_offset); if (optv > 1) { @@ -2176,7 +2188,9 @@ static int redo_delete_index(ntfs_volume *vol, && !(length & 7) && ((target + length) <= xsize)) { /* This has to be an idempotent action */ - found = !memcmp(buffer + target, data, length); + found = (action->record.undo_operation + == const_cpu_to_le16(CompensationlogRecord)) + || !memcmp(buffer + target, data, length); err = 0; if (found) { /* Remove the entry */ @@ -2191,7 +2205,7 @@ static int redo_delete_index(ntfs_volume *vol, } if (optv > 1) { printf("-> INDX record %s\n", - (found ? "unchanged" : "removed")); + (found ? "removed" : "unchanged")); } } return (err); @@ -2238,7 +2252,9 @@ static int redo_delete_root_index(ntfs_volume *vol, && !(length & 7) && ((target + length) <= mftrecsz)) { /* This has to be an idempotent action */ - found = !memcmp(buffer + target, data, length); + found = (action->record.undo_operation + == const_cpu_to_le16(CompensationlogRecord)) + || !memcmp(buffer + target, data, length); err = 0; /* Only delete if present */ if (found) { @@ -2578,7 +2594,9 @@ static int redo_update_resident(ntfs_volume *vol, dump(&buffer[target], length); } if ((target + length) <= mftrecsz) { - changed = memcmp(buffer + target, data, length); + changed = (action->record.undo_operation + == const_cpu_to_le16(CompensationlogRecord)) + || memcmp(buffer + target, data, length); err = 0; if (changed) { memcpy(buffer + target, data, length); @@ -2627,8 +2645,13 @@ static int redo_update_root_index(ntfs_volume *vol, + le16_to_cpu(action->record.attribute_offset) + offsetof(INDEX_ENTRY, key.file_name.file_name_length) - length; - err = change_resident_expect(vol, action, buffer, data, expected, - target, length, AT_INDEX_ROOT); + if (action->record.undo_operation + == const_cpu_to_le16(CompensationlogRecord)) + err = change_resident(vol, action, buffer, data, + target, length); + else + err = change_resident_expect(vol, action, buffer, data, + expected, target, length, AT_INDEX_ROOT); return (err); } @@ -2649,12 +2672,14 @@ static int redo_update_root_vcn(ntfs_volume *vol, expected = ((const char*)&action->record) + get_undo_offset(&action->record); length = le16_to_cpu(action->record.redo_length); -// length must be 8 - target = le16_to_cpu(action->record.record_offset) - + le16_to_cpu(action->record.attribute_offset) -+ 16; // explanation needed (right justified ?) - err = change_resident_expect(vol, action, buffer, data, expected, - target, length, AT_INDEX_ROOT); + if (length == 8) { + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + /* target is right-justified to end of attribute */ + target += getle16(buffer, target + 8) - length; + err = change_resident_expect(vol, action, buffer, data, + expected, target, length, AT_INDEX_ROOT); + } return (err); } @@ -2738,11 +2763,13 @@ static int redo_update_vcn(ntfs_volume *vol, data = ((const char*)&action->record) + get_redo_offset(&action->record); length = le16_to_cpu(action->record.redo_length); - /* target is left-justified to creation time */ - target = le16_to_cpu(action->record.record_offset) - + le16_to_cpu(action->record.attribute_offset) - + 16; // to better describe - err = update_index(vol, action, buffer, data, target, length); + if (length == 8) { + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + /* target is right-justified to end of attribute */ + target += getle16(buffer, target + 8) - length; + err = update_index(vol, action, buffer, data, target, length); + } return (err); } @@ -3568,11 +3595,13 @@ static int undo_update_vcn(ntfs_volume *vol, const struct ACTION_RECORD *action, data = ((const char*)&action->record) + get_undo_offset(&action->record); length = le16_to_cpu(action->record.undo_length); - /* target is left-justified to creation time */ - target = le16_to_cpu(action->record.record_offset) - + le16_to_cpu(action->record.attribute_offset) - + 16; // to better describe - err = update_index(vol, action, buffer, data, target, length); + if (length == 8) { + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + /* target is right-justified to end of attribute */ + target += getle16(buffer, target + 8) - length; + err = update_index(vol, action, buffer, data, target, length); + } return (err); } @@ -3775,12 +3804,14 @@ static int undo_update_root_vcn(ntfs_volume *vol, expected = ((const char*)&action->record) + get_redo_offset(&action->record); length = le16_to_cpu(action->record.undo_length); - /* the fixup is right-justified to the name length */ - target = le16_to_cpu(action->record.record_offset) - + le16_to_cpu(action->record.attribute_offset) - + 16; // explanation needed - err = change_resident_expect(vol, action, buffer, data, expected, - target, length, AT_INDEX_ROOT); + if (length == 8) { + target = le16_to_cpu(action->record.record_offset) + + le16_to_cpu(action->record.attribute_offset); + /* target is right-justified to end of attribute */ + target += getle16(buffer, target + 8) - length; + err = change_resident_expect(vol, action, buffer, data, + expected, target, length, AT_INDEX_ROOT); + } return (err); } @@ -4085,7 +4116,9 @@ static int distribute_redos(ntfs_volume *vol, break; case ClearBitsInNonResidentBitMap : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(SetBitsInNonResidentBitMap))) + const_cpu_to_le16(SetBitsInNonResidentBitMap)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_force_bits(vol, action, buffer); break; case CompensationlogRecord : @@ -4095,27 +4128,37 @@ static int distribute_redos(ntfs_volume *vol, break; case CreateAttribute : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(DeleteAttribute))) + const_cpu_to_le16(DeleteAttribute)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_create_attribute(vol, action, buffer); break; case DeallocateFileRecordSegment : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(InitializeFileRecordSegment))) + const_cpu_to_le16(InitializeFileRecordSegment)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_delete_file(vol, action, buffer); break; case DeleteAttribute : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(CreateAttribute))) + const_cpu_to_le16(CreateAttribute)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_delete_attribute(vol, action, buffer); break; case DeleteIndexEntryAllocation : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(AddIndexEntryAllocation))) + const_cpu_to_le16(AddIndexEntryAllocation)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_delete_index(vol, action, buffer); break; case DeleteIndexEntryRoot : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(AddIndexEntryRoot))) + const_cpu_to_le16(AddIndexEntryRoot)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_delete_root_index(vol, action, buffer); break; case InitializeFileRecordSegment : @@ -4135,7 +4178,9 @@ static int distribute_redos(ntfs_volume *vol, break; case SetIndexEntryVcnAllocation : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(SetIndexEntryVcnAllocation))) + const_cpu_to_le16(SetIndexEntryVcnAllocation)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_update_vcn(vol, action, buffer); break; case SetIndexEntryVcnRoot : @@ -4145,17 +4190,23 @@ static int distribute_redos(ntfs_volume *vol, break; case SetNewAttributeSizes : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(SetNewAttributeSizes))) + const_cpu_to_le16(SetNewAttributeSizes)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_sizes(vol, action, buffer); break; case UpdateFileNameAllocation : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(UpdateFileNameAllocation))) + const_cpu_to_le16(UpdateFileNameAllocation)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_update_index(vol, action, buffer); break; case UpdateFileNameRoot : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(UpdateFileNameRoot))) + const_cpu_to_le16(UpdateFileNameRoot)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_update_root_index(vol, action, buffer); break; case UpdateMappingPairs : @@ -4178,7 +4229,9 @@ static int distribute_redos(ntfs_volume *vol, break; case UpdateResidentValue : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(UpdateResidentValue))) + const_cpu_to_le16(UpdateResidentValue)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_update_resident(vol, action, buffer); break; case Win10Action37 : @@ -4193,7 +4246,9 @@ static int distribute_redos(ntfs_volume *vol, break; case WriteEndOfIndexBuffer : if (le16_eq(action->record.undo_operation, - const_cpu_to_le16(WriteEndOfIndexBuffer))) + const_cpu_to_le16(WriteEndOfIndexBuffer)) + || le16_eq(action->record.undo_operation, + const_cpu_to_le16(CompensationlogRecord))) err = redo_write_index(vol, action, buffer); break; case AttributeNamesDump : diff --git a/ntfsprogs/utils.c b/ntfsprogs/utils.c index b0a1843b..e973cb4a 100644 --- a/ntfsprogs/utils.c +++ b/ntfsprogs/utils.c @@ -75,7 +75,7 @@ #include "logging.h" #include "misc.h" -const char *ntfs_bugs = "Developers' email address: "NTFS_DEV_LIST"\n"; +const char *ntfs_bugs = "Developers' email address: " NTFS_DEV_LIST "\n"; const char *ntfs_gpl = "This program is free software, released under the GNU " "General Public License\nand you are welcome to redistribute it under " "certain conditions. It comes with\nABSOLUTELY NO WARRANTY; for " @@ -364,9 +364,13 @@ int utils_parse_size(const char *value, s64 *size, BOOL scale) if (scale) { switch (suffix[0]) { case 't': case 'T': result *= 1000; + /* FALLTHRU */ case 'g': case 'G': result *= 1000; + /* FALLTHRU */ case 'm': case 'M': result *= 1000; + /* FALLTHRU */ case 'k': case 'K': result *= 1000; + /* FALLTHRU */ case '-': case 0: break; default: diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 9e5c2533..b0ac8a37 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-2017 Jean-Pierre Andre + * Copyright (c) 2007-2021 Jean-Pierre Andre * Copyright (c) 2009 Erik Larsson * * This file is originated from the Linux-NTFS project. @@ -86,10 +86,6 @@ #include #endif /* defined(__APPLE__) || defined(__DARWIN__), ... */ -#ifdef HAVE_LINUX_FS_H -#include -#endif - #ifndef FUSE_CAP_POSIX_ACL /* until defined in */ #define FUSE_CAP_POSIX_ACL (1 << 18) #endif /* FUSE_CAP_POSIX_ACL */ @@ -106,6 +102,7 @@ #include "ntfstime.h" #include "security.h" #include "reparse.h" +#include "ea.h" #include "object_id.h" #include "efs.h" #include "logging.h" @@ -133,12 +130,12 @@ #endif #if !CACHEING -#define ATTR_TIMEOUT 0.0 -#define ENTRY_TIMEOUT 0.0 +#define ATTR_TIMEOUT (ctx->ro ? TIMEOUT_RO : 0.0) +#define ENTRY_TIMEOUT (ctx->ro ? TIMEOUT_RO : 0.0) #else #if defined(__sun) && defined (__SVR4) -#define ATTR_TIMEOUT 10.0 -#define ENTRY_TIMEOUT 10.0 +#define ATTR_TIMEOUT (ctx->ro ? TIMEOUT_RO : 10.0) +#define ENTRY_TIMEOUT (ctx->ro ? TIMEOUT_RO : 10.0) #else /* defined(__sun) && defined (__SVR4) */ /* * FUSE cacheing is only usable with basic permissions @@ -148,11 +145,13 @@ #warning "Fuse cacheing is only usable with basic permissions checked by kernel" #endif #if KERNELACLS -#define ATTR_TIMEOUT 10.0 -#define ENTRY_TIMEOUT 10.0 +#define ATTR_TIMEOUT (ctx->ro ? TIMEOUT_RO : 10.0) +#define ENTRY_TIMEOUT (ctx->ro ? TIMEOUT_RO : 10.0) #else /* KERNELACLS */ -#define ATTR_TIMEOUT (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 10.0 : 0.0) -#define ENTRY_TIMEOUT (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 10.0 : 0.0) +#define ATTR_TIMEOUT (ctx->ro ? TIMEOUT_RO : \ + (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 10.0 : 0.0)) +#define ENTRY_TIMEOUT (ctx->ro ? TIMEOUT_RO : \ + (ctx->vol->secure_flags & (1 << SECURITY_DEFAULT) ? 10.0 : 0.0)) #endif /* KERNELACLS */ #endif /* defined(__sun) && defined (__SVR4) */ #endif /* !CACHEING */ @@ -203,6 +202,9 @@ typedef struct fill_item { typedef struct fill_context { struct fill_item *first; struct fill_item *last; +#ifndef DISABLE_PLUGINS + u64 fh; +#endif /* DISABLE_PLUGINS */ off_t off; fuse_req_t req; fuse_ino_t ino; @@ -260,7 +262,7 @@ static const char *usage_msg = "\n" "Copyright (C) 2005-2007 Yura Pakhuchiy\n" "Copyright (C) 2006-2009 Szabolcs Szakacsits\n" -"Copyright (C) 2007-2017 Jean-Pierre Andre\n" +"Copyright (C) 2007-2021 Jean-Pierre Andre\n" "Copyright (C) 2009 Erik Larsson\n" "\n" "Usage: %s [-o option[,...]] \n" @@ -269,11 +271,16 @@ static const char *usage_msg = " 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" +"Example: lowntfs-3g /dev/sda1 /mnt/windows\n" "\n" +#ifdef PLUGIN_DIR +"Plugin path: " PLUGIN_DIR "\n\n" +#endif /* PLUGIN_DIR */ "%s"; -static const char ntfs_bad_reparse[] = "unsupported reparse point"; +static const char ntfs_bad_reparse[] = "unsupported reparse tag 0x%08lx"; + /* exact length of target text, without the terminator */ +#define ntfs_bad_reparse_lth (sizeof(ntfs_bad_reparse) + 2) #ifdef FUSE_INTERNAL int drop_privs(void); @@ -660,7 +667,7 @@ static int junction_getstat(ntfs_inode *ni, if (target) stbuf->st_size = strlen(target); else - stbuf->st_size = sizeof(ntfs_bad_reparse) - 1; + stbuf->st_size = ntfs_bad_reparse_lth; stbuf->st_blocks = (ni->allocated_size + 511) >> 9; stbuf->st_mode = S_IFLNK; free(target); @@ -671,6 +678,49 @@ static int junction_getstat(ntfs_inode *ni, return (res); } +static int wsl_getstat(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct stat *stbuf) +{ + dev_t rdev; + int res; + + res = ntfs_reparse_check_wsl(ni, reparse); + if (!res) { + /* switch (reparse->reparse_tag) { */ + if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_AF_UNIX)) { + stbuf->st_mode = S_IFSOCK; + } + else if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_FIFO)) { + stbuf->st_mode = S_IFIFO; + } + else if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_CHR)) { + stbuf->st_mode = S_IFCHR; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + } + else if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_BLK)) { + stbuf->st_mode = S_IFBLK; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + } + else { + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + } + /* } */ + } + /* + * If the reparse point is not a valid wsl special file + * we display as a symlink + */ + if (res) { + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + res = 0; + } + return (res); +} + /* * Apply permission masks to st_mode returned by reparse handler */ @@ -704,6 +754,9 @@ 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 (ctx->posix_nlink + && le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) + stbuf->st_nlink = ntfs_dir_link_cnt(ni); if (!le16_andz(ni->mrec->flags, MFT_RECORD_IS_DIRECTORY) || !le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { if (!le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { @@ -715,8 +768,7 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, if (!res) { apply_umask(stbuf); } else { - stbuf->st_size = - sizeof(ntfs_bad_reparse) - 1; + stbuf->st_size = ntfs_bad_reparse_lth; stbuf->st_blocks = (ni->allocated_size + 511) >> 9; stbuf->st_mode = S_IFLNK; @@ -737,8 +789,7 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, if (target) stbuf->st_size = strlen(target); else - stbuf->st_size = - sizeof(ntfs_bad_reparse) - 1; + stbuf->st_size = ntfs_bad_reparse_lth; stbuf->st_blocks = (ni->allocated_size + 511) >> 9; stbuf->st_nlink = @@ -766,7 +817,8 @@ static int ntfs_fuse_getstat(struct SECURITY_CONTEXT *scx, } stbuf->st_size = ni->data_size; stbuf->st_blocks = ni->allocated_size >> 9; - stbuf->st_nlink = 1; /* Make find(1) work */ + if (!ctx->posix_nlink) + stbuf->st_nlink = 1; /* Make find(1) work */ } } else { /* Regular or Interix (INTX) file. */ @@ -1027,19 +1079,33 @@ static void ntfs_fuse_lookup(fuse_req_t req, fuse_ino_t parent, */ static int junction_readlink(ntfs_inode *ni, - const REPARSE_POINT *reparse __attribute__((unused)), - char **pbuf) + const REPARSE_POINT *reparse, char **pbuf) { int res; + le32 tag; + int lth; errno = 0; res = 0; *pbuf = ntfs_make_symlink(ni, ctx->abs_mnt_point); if (!*pbuf) { if (errno == EOPNOTSUPP) { - *pbuf = strdup(ntfs_bad_reparse); - if (!*pbuf) - res = -errno; + *pbuf = (char*)ntfs_malloc(ntfs_bad_reparse_lth + 1); + if (*pbuf) { + if (reparse) + tag = reparse->reparse_tag; + else + tag = const_cpu_to_le32(0); + lth = snprintf(*pbuf, ntfs_bad_reparse_lth + 1, + ntfs_bad_reparse, + (long)le32_to_cpu(tag)); + if (lth != ntfs_bad_reparse_lth) { + free(*pbuf); + *pbuf = (char*)NULL; + res = -errno; + } + } else + res = -ENOMEM; } else res = -errno; } @@ -1066,27 +1132,42 @@ static void ntfs_fuse_readlink(fuse_req_t req, fuse_ino_t ino) * Reparse point : analyze as a junction point */ if (!le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { + REPARSE_POINT *reparse; + le32 tag; + int lth; #ifndef DISABLE_PLUGINS 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; - } + /* plugin missing or reparse tag failing the check */ + if (res && ((errno == ELIBACC) || (errno == EINVAL))) + errno = EOPNOTSUPP; #else /* DISABLE_PLUGINS */ errno = 0; res = 0; buf = ntfs_make_symlink(ni, ctx->abs_mnt_point); - if (!buf) { - if (errno == EOPNOTSUPP) - buf = strdup(ntfs_bad_reparse); - if (!buf) - res = -errno; - } #endif /* DISABLE_PLUGINS */ + if (!buf && (errno == EOPNOTSUPP)) { + buf = (char*)malloc(ntfs_bad_reparse_lth + 1); + if (buf) { + reparse = ntfs_get_reparse_point(ni); + if (reparse) { + tag = reparse->reparse_tag; + free(reparse); + } else + tag = const_cpu_to_le32(0); + lth = snprintf(buf, ntfs_bad_reparse_lth + 1, + ntfs_bad_reparse, + (long)le32_to_cpu(tag)); + res = 0; + if (lth != ntfs_bad_reparse_lth) { + free(buf); + buf = (char*)NULL; + } + } + } + if (!buf) + res = -errno; goto exit; } /* Sanity checks. */ @@ -1142,8 +1223,7 @@ exit: fuse_reply_err(req, -res); else fuse_reply_readlink(req, buf); - if (buf != ntfs_bad_reparse) - free(buf); + free(buf); } static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, @@ -1169,6 +1249,9 @@ static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, /* never return inodes 0 and 1 */ if (MREF(mref) > 1) { struct stat st = { .st_ino = MREF(mref) }; +#ifndef DISABLE_PLUGINS + ntfs_inode *ni; +#endif /* DISABLE_PLUGINS */ switch (dt_type) { case NTFS_DT_DIR : @@ -1189,6 +1272,26 @@ static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, case NTFS_DT_CHR : st.st_mode = S_IFCHR; break; + case NTFS_DT_REPARSE : + st.st_mode = S_IFLNK | 0777; /* default */ +#ifndef DISABLE_PLUGINS + /* get emulated type from plugin if available */ + ni = ntfs_inode_open(ctx->vol, mref); + if (ni && !le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + int res; + + res = CALL_REPARSE_PLUGIN(ni, getattr, &st); + if (!res) + apply_umask(&st); + else + st.st_mode = S_IFLNK; + } + if (ni) + ntfs_inode_close(ni); +#endif /* DISABLE_PLUGINS */ + break; default : /* unexpected types shown as plain files */ case NTFS_DT_REG : st.st_mode = S_IFREG | (0777 & ~ctx->fmask); @@ -1292,6 +1395,17 @@ static void ntfs_fuse_opendir(fuse_req_t req, fuse_ino_t ino, if (!ntfs_allowed_access(&security,ni,accesstype)) res = -EACCES; } + if (!le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + fi->fh = 0; + res = CALL_REPARSE_PLUGIN(ni, opendir, fi); +#else /* DISABLE_PLUGINS */ + res = -EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } if (ntfs_inode_close(ni)) set_fuse_error(&res); if (!res) { @@ -1305,6 +1419,9 @@ static void ntfs_fuse_opendir(fuse_req_t req, fuse_ino_t ino, fill->filled = FALSE; fill->ino = ino; fill->off = 0; +#ifndef DISABLE_PLUGINS + fill->fh = fi->fh; +#endif /* DISABLE_PLUGINS */ } fi->fh = (long)fill; } @@ -1321,9 +1438,15 @@ static void ntfs_fuse_releasedir(fuse_req_t req, fuse_ino_t ino __attribute__((unused)), struct fuse_file_info *fi) { +#ifndef DISABLE_PLUGINS + struct fuse_file_info ufi; + ntfs_inode *ni; +#endif /* DISABLE_PLUGINS */ ntfs_fuse_fill_context_t *fill; ntfs_fuse_fill_item_t *current; + int res; + res = 0; fill = (ntfs_fuse_fill_context_t*)(long)fi->fh; if (fill && (fill->ino == ino)) { /* make sure to clear results */ @@ -1333,16 +1456,38 @@ static void ntfs_fuse_releasedir(fuse_req_t req, free(fill->first); fill->first = current; } +#ifndef DISABLE_PLUGINS + if (fill->fh) { + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + ni = ntfs_inode_open(ctx->vol, INODE(ino)); + if (ni) { + if (!le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { + memcpy(&ufi, fi, sizeof(ufi)); + ufi.fh = fill->fh; + res = CALL_REPARSE_PLUGIN(ni, release, + &ufi); + } + if (ntfs_inode_close(ni) && !res) + res = -errno; + } else + res = -errno; + } +#endif /* DISABLE_PLUGINS */ fill->ino = 0; free(fill); } - fuse_reply_err(req, 0); + fuse_reply_err(req, -res); } 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))) { +#ifndef DISABLE_PLUGINS + struct fuse_file_info ufi; +#endif /* DISABLE_PLUGINS */ ntfs_fuse_fill_item_t *first; ntfs_fuse_fill_item_t *current; ntfs_fuse_fill_context_t *fill; @@ -1379,10 +1524,27 @@ static void ntfs_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, if (!ni) err = -errno; else { - if (ntfs_readdir(ni, &pos, fill, - (ntfs_filldir_t) + if (!le32_andz(ni->flags, + FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + memcpy(&ufi, fi, sizeof(ufi)); + ufi.fh = fill->fh; + err = CALL_REPARSE_PLUGIN(ni, + readdir, &pos, fill, + (ntfs_filldir_t) + ntfs_fuse_filler, &ufi); +#else /* DISABLE_PLUGINS */ + err = -EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } else { + if (ntfs_readdir(ni, &pos, fill, + (ntfs_filldir_t) ntfs_fuse_filler)) - err = -errno; + err = -errno; + } fill->filled = TRUE; ntfs_fuse_update_times(ni, NTFS_UPDATE_ATIME); @@ -1927,15 +2089,41 @@ static int ntfs_fuse_utimens(struct SECURITY_CONTEXT *scx, fuse_ino_t ino, if (to_set & FUSE_SET_ATTR_ATIME_NOW) mask |= NTFS_UPDATE_ATIME; else - if (to_set & FUSE_SET_ATTR_ATIME) + if (to_set & FUSE_SET_ATTR_ATIME) { +#ifdef HAVE_STRUCT_STAT_ST_ATIMESPEC + ni->last_access_time + = timespec2ntfs(stin->st_atimespec); +#elif defined(HAVE_STRUCT_STAT_ST_ATIM) ni->last_access_time = timespec2ntfs(stin->st_atim); +#else + ni->last_access_time.tv_sec + = stin->st_atime; +#ifdef HAVE_STRUCT_STAT_ST_ATIMENSEC + ni->last_access_time.tv_nsec + = stin->st_atimensec; +#endif +#endif + } if (to_set & FUSE_SET_ATTR_MTIME_NOW) mask |= NTFS_UPDATE_MTIME; else - if (to_set & FUSE_SET_ATTR_MTIME) + if (to_set & FUSE_SET_ATTR_MTIME) { +#ifdef HAVE_STRUCT_STAT_ST_ATIMESPEC + ni->last_data_change_time + = timespec2ntfs(stin->st_mtimespec); +#elif defined(HAVE_STRUCT_STAT_ST_ATIM) ni->last_data_change_time = timespec2ntfs(stin->st_mtim); +#else + ni->last_data_change_time.tv_sec + = stin->st_mtime; +#ifdef HAVE_STRUCT_STAT_ST_ATIMENSEC + ni->last_data_change_time.tv_nsec + = stin->st_mtimensec; +#endif +#endif + } ntfs_inode_update_times(ni, mask); #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) } else @@ -2197,7 +2385,11 @@ static int ntfs_fuse_create(fuse_req_t req, fuse_ino_t parent, const char *name, perm = (typemode & ~ctx->dmask & 0777) | (dsetgid & S_ISGID); else - perm = typemode & ~ctx->fmask & 0777; + if ((ctx->special_files == NTFS_FILES_WSL) + && S_ISLNK(type)) + perm = typemode | 0777; + else + perm = typemode & ~ctx->fmask & 0777; /* * Try to get a security id available for * file creation (from inheritance or argument). @@ -2221,26 +2413,50 @@ static int ntfs_fuse_create(fuse_req_t req, fuse_ino_t parent, const char *name, 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 (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + reparse = (REPARSE_POINT*)NULL; + ops = select_reparse_plugin(ctx, dir_ni, &reparse); + if (ops && ops->create) { + ni = (*ops->create)(dir_ni, reparse, + securid, uname, uname_len, type); + } else { + ni = (ntfs_inode*)NULL; + errno = EOPNOTSUPP; + } + free(reparse); +#else /* DISABLE_PLUGINS */ + ni = (ntfs_inode*)NULL; + errno = EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } else { + 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) { /* @@ -2410,10 +2626,25 @@ static int ntfs_fuse_newlink(fuse_req_t req __attribute__((unused)), #else ntfs_fuse_fill_security_context(req, &security); #endif - { - if (ntfs_link(ni, dir_ni, uname, uname_len)) { - res = -errno; + { + if (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(dir_ni, link, + ni, uname, uname_len); + if (res < 0) + goto exit; +#else /* DISABLE_PLUGINS */ + res = -EOPNOTSUPP; goto exit; +#endif /* DISABLE_PLUGINS */ + } else { + 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) { @@ -2468,6 +2699,9 @@ static int ntfs_fuse_rm(fuse_req_t req, fuse_ino_t parent, const char *name, #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) struct SECURITY_CONTEXT security; #endif +#if defined(__sun) && defined (__SVR4) + int isdir; +#endif /* defined(__sun) && defined (__SVR4) */ /* Deny removing from $Extend */ if (parent == FILE_Extend) { @@ -2507,9 +2741,32 @@ static int ntfs_fuse_rm(fuse_req_t req, fuse_ino_t parent, const char *name, #if defined(__sun) && defined (__SVR4) /* on Solaris : deny unlinking directories */ - if (rm_type - == (!le16_andz(ni->mrec->flags, MFT_RECORD_IS_DIRECTORY) ? RM_LINK : RM_DIR)) { - errno = EPERM; + isdir = !le16_andz(ni->mrec->flags, MFT_RECORD_IS_DIRECTORY); +#ifndef DISABLE_PLUGINS + /* get emulated type from plugin if available */ + if (!le32_andz((ni->flags, FILE_ATTR_REPARSE_POINT) { + struct stat st; + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + /* Avoid double opening of parent directory */ + res = ntfs_inode_close(dir_ni); + if (res) + goto exit; + dir_ni = (ntfs_inode*)NULL; + res = CALL_REPARSE_PLUGIN(ni, getattr, &st); + if (res) + goto exit; + isdir = S_ISDIR(st.st_mode); + dir_ni = ntfs_inode_open(ctx->vol, INODE(parent)); + if (!dir_ni) { + res = -errno; + goto exit; + } + } +#endif /* DISABLE_PLUGINS */ + if (rm_type == (isdir ? RM_LINK : RM_DIR)) { + errno = (isdir ? EISDIR : ENOTDIR); res = -errno; goto exit; } @@ -2597,9 +2854,20 @@ static int ntfs_fuse_rm(fuse_req_t req, fuse_ino_t parent, const char *name, goto exit; } } - if (ntfs_delete(ctx->vol, (char*)NULL, ni, dir_ni, - uname, uname_len)) - res = -errno; + if (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(dir_ni, unlink, (char*)NULL, + ni, uname, uname_len); +#else /* DISABLE_PLUGINS */ + res = -EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } else + 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: @@ -2949,7 +3217,14 @@ static void ntfs_fuse_ioctl(fuse_req_t req __attribute__((unused)), } memcpy(buf, data, in_bufsz); } - ret = ntfs_ioctl(ni, cmd, arg, flags, buf); + /* + * Linux defines the request argument of ioctl() to be an + * unsigned long, which fuse 2.x forwards as a signed int + * into which the request sometimes does not fit. + * So we must expand the value and make sure it is not + * sign-extended. + */ + ret = ntfs_ioctl(ni, (unsigned int)cmd, arg, flags, buf); if (ntfs_inode_close (ni)) set_fuse_error(&ret); } @@ -3064,7 +3339,7 @@ static ntfs_inode *ntfs_check_access_xattr(fuse_req_t req, || !(ctx->secure_flags & (1 << SECURITY_ACL)) || (setting && ctx->inherit)) && foracl) { - if (ctx->silent) + if (ctx->silent && !ctx->security.mapping[MAPUSERS]) errno = 0; else errno = EOPNOTSUPP; @@ -3251,9 +3526,18 @@ out : free(list); } +#if defined(__APPLE__) || defined(__DARWIN__) +static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size, uint32_t position) +#else static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) +#endif { +#if !(defined(__APPLE__) || defined(__DARWIN__)) + static const unsigned int position = 0U; +#endif + ntfs_inode *ni; ntfs_inode *dir_ni; ntfs_attr *na = NULL; @@ -3266,6 +3550,16 @@ static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, int namespace; struct SECURITY_CONTEXT security; +#if defined(__APPLE__) || defined(__DARWIN__) + /* If the attribute is not a resource fork attribute and the position + * parameter is non-zero, we return with EINVAL as requesting position + * is not permitted for non-resource fork attributes. */ + if (position && strcmp(name, XATTR_RESOURCEFORK_NAME)) { + fuse_reply_err(req, EINVAL); + return; + } +#endif + attr = ntfs_xattr_system_type(name,ctx->vol); if (attr != XATTR_UNMAPPED) { /* @@ -3343,7 +3637,7 @@ static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, /* trusted only readable by root */ if ((namespace == XATTRNS_TRUSTED) && security.uid) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto out; } #endif @@ -3354,7 +3648,7 @@ static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, } /* Return with no result for symlinks, fifo, etc. */ if (!user_xattrs_allowed(ctx, ni)) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto exit; } /* otherwise file must be readable */ @@ -3371,7 +3665,7 @@ static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, } na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); if (!na) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto exit; } rsize = na->data_size; @@ -3380,11 +3674,13 @@ static void ntfs_fuse_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, && !le16_andz(na->data_flags, ATTR_IS_ENCRYPTED) && NAttrNonResident(na)) rsize = ((na->data_size + 511) & ~511) + 2; + rsize -= position; if (size) { if (size >= (size_t)rsize) { value = (char*)ntfs_malloc(rsize); if (value) - res = ntfs_attr_pread(na, 0, rsize, value); + res = ntfs_attr_pread(na, position, rsize, + value); if (!value || (res != rsize)) res = -errno; } else @@ -3409,9 +3705,21 @@ out : free(value); } +#if defined(__APPLE__) || defined(__DARWIN__) +static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags, + uint32_t position) +#else static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags) +#endif { +#if !(defined(__APPLE__) || defined(__DARWIN__)) + static const unsigned int position = 0U; +#else + BOOL is_resource_fork; +#endif + ntfs_inode *ni; ntfs_inode *dir_ni; ntfs_attr *na = NULL; @@ -3423,6 +3731,17 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, int namespace; struct SECURITY_CONTEXT security; +#if defined(__APPLE__) || defined(__DARWIN__) + /* If the attribute is not a resource fork attribute and the position + * parameter is non-zero, we return with EINVAL as requesting position + * is not permitted for non-resource fork attributes. */ + is_resource_fork = strcmp(name, XATTR_RESOURCEFORK_NAME) ? FALSE : TRUE; + if (position && !is_resource_fork) { + fuse_reply_err(req, EINVAL); + return; + } +#endif + attr = ntfs_xattr_system_type(name,ctx->vol); if (attr != XATTR_UNMAPPED) { /* @@ -3490,7 +3809,7 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, } else res = -errno; #endif -#if CACHEING && !defined(FUSE_INTERNAL) +#if CACHEING && !defined(FUSE_INTERNAL) && FUSE_VERSION >= 28 /* * Most of system xattr settings cause changes to some * file attribute (st_mode, st_nlink, st_mtime, etc.), @@ -3582,7 +3901,7 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, } if (!na) { if (flags == XATTR_REPLACE) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto exit; } if (ntfs_attr_add(ni, AT_DATA, lename, lename_len, NULL, 0)) { @@ -3598,6 +3917,11 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, res = -errno; goto exit; } +#if defined(__APPLE__) || defined(__DARWIN__) + } else if (is_resource_fork) { + /* In macOS, the resource fork is a special case. It doesn't + * ever shrink (it would have to be removed and re-added). */ +#endif } else { /* currently compressed streams can only be wiped out */ if (ntfs_attr_truncate(na, (s64)0 /* size */)) { @@ -3609,8 +3933,8 @@ static void ntfs_fuse_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, res = 0; if (size) { do { - part = ntfs_attr_pwrite(na, total, size - total, - &value[total]); + part = ntfs_attr_pwrite(na, position + total, + size - total, &value[total]); if (part > 0) total += part; } while ((part > 0) && (total < size)); @@ -3732,7 +4056,7 @@ static void ntfs_fuse_removexattr(fuse_req_t req, fuse_ino_t ino, const char *na } else res = -errno; #endif -#if CACHEING && !defined(FUSE_INTERNAL) +#if CACHEING && !defined(FUSE_INTERNAL) && FUSE_VERSION >= 28 /* * Some allowed system xattr removals cause changes to * some file attribute (st_mode, st_nlink, etc.), @@ -3820,7 +4144,7 @@ static void ntfs_fuse_removexattr(fuse_req_t req, fuse_ino_t ino, const char *na } if (ntfs_attr_remove(ni, AT_DATA, lename, lename_len)) { if (errno == ENOENT) - errno = ENODATA; + errno = NTFS_NOXATTR_ERRNO; res = -errno; } if (!res) { @@ -3856,10 +4180,23 @@ static void register_internal_reparse_plugins(void) .getattr = junction_getstat, .readlink = junction_readlink, } ; + static const plugin_operations_t wsl_ops = { + .getattr = wsl_getstat, + } ; register_reparse_plugin(ctx, IO_REPARSE_TAG_MOUNT_POINT, &ops, (void*)NULL); register_reparse_plugin(ctx, IO_REPARSE_TAG_SYMLINK, &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_SYMLINK, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_AF_UNIX, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_FIFO, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_CHR, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_BLK, + &wsl_ops, (void*)NULL); } #endif /* DISABLE_PLUGINS */ @@ -4323,7 +4660,8 @@ int main(int argc, char *argv[]) else { ctx->abs_mnt_point = (char*)ntfs_malloc(PATH_MAX); if (ctx->abs_mnt_point) { - if (getcwd(ctx->abs_mnt_point, + if ((strlen(opts.mnt_point) < PATH_MAX) + && 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); @@ -4331,6 +4669,9 @@ int main(int argc, char *argv[]) /* Solaris also wants the absolute mount point */ opts.mnt_point = ctx->abs_mnt_point; #endif /* defined(__sun) && defined (__SVR4) */ + } else { + free(ctx->abs_mnt_point); + ctx->abs_mnt_point = (char*)NULL; } } } @@ -4387,14 +4728,20 @@ int main(int argc, char *argv[]) /* Force read-only mount if the device was found read-only */ if (!ctx->ro && NVolReadOnly(ctx->vol)) { + ctx->rw = FALSE; ctx->ro = TRUE; if (ntfs_strinsert(&parsed_options, ",ro")) goto err_out; - } + ntfs_log_info("Could not mount read-write, trying read-only\n"); + } else + if (ctx->rw && ntfs_strinsert(&parsed_options, ",rw")) + 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->vol->abs_mnt_point = ctx->abs_mnt_point; + ctx->vol->special_files = ctx->special_files; ctx->security.vol = ctx->vol; ctx->vol->secure_flags = ctx->secure_flags; #ifdef HAVE_SETXATTR /* extended attributes interface required */ diff --git a/src/ntfs-3g.8.in b/src/ntfs-3g.8.in index a3fb0dad..d4338a9c 100644 --- a/src/ntfs-3g.8.in +++ b/src/ntfs-3g.8.in @@ -52,6 +52,8 @@ hibernation and fast restarting : powercfg /h off .sp .RE +If either Windows is hibernated or its fast restart is enabled, partitions +on internal disks are forced to be mounted in read-only mode. .SS Access Handling and Security By default, files and directories are owned by the effective user and group of the mounting process, and everybody has @@ -248,6 +250,13 @@ they do not appear in Windows directory displays either. When a file is renamed or linked with a new name, the hidden flag is adjusted to the latest name. .TP +.B posix_nlink +Compute the count of hard links of a file or directory according to +the Posix specifications. When this option is not set, a count of 1 +is set for directories, and the short name of files is accounted for. +Using the option entails some penalty as the count is not stored and +has to be computed. +.TP .B windows_names This option prevents files, directories and extended attributes to be created with a name not allowed by windows, because @@ -307,6 +316,14 @@ data streams are mapped to xattrs and user can manipulate them using .B user_xattr Same as \fBstreams_interface=\fP\fIxattr\fP. .TP +.BI special_files= value +This option selects a mode for representing a special file to be created +(symbolic link, socket, fifo, character or block device). The mode can +be \fBinterix\fR or \fBwsl\fR, and existing files in either mode are +recognized irrespective of the selected mode. Interix is the traditional +mode, used by default, and wsl is interoperable with Windows WSL, but +it is not compatible with Windows versions earlier than Windows 10. +.TP .B efs_raw This option should only be used in backup or restore situation. It changes the apparent size of files and the behavior of read and diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index 813474e2..f77a016f 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-2017 Jean-Pierre Andre + * Copyright (c) 2007-2021 Jean-Pierre Andre * Copyright (c) 2009 Erik Larsson * * This file is originated from the Linux-NTFS project. @@ -100,6 +100,7 @@ #include "ntfstime.h" #include "security.h" #include "reparse.h" +#include "ea.h" #include "object_id.h" #include "efs.h" #include "logging.h" @@ -196,7 +197,7 @@ static const char *usage_msg = "\n" "Copyright (C) 2005-2007 Yura Pakhuchiy\n" "Copyright (C) 2006-2009 Szabolcs Szakacsits\n" -"Copyright (C) 2007-2017 Jean-Pierre Andre\n" +"Copyright (C) 2007-2021 Jean-Pierre Andre\n" "Copyright (C) 2009 Erik Larsson\n" "\n" "Usage: %s [-o option[,...]] \n" @@ -207,9 +208,14 @@ static const char *usage_msg = "\n" "Example: ntfs-3g /dev/sda1 /mnt/windows\n" "\n" +#ifdef PLUGIN_DIR +"Plugin path: " PLUGIN_DIR "\n\n" +#endif /* PLUGIN_DIR */ "%s"; -static const char ntfs_bad_reparse[] = "unsupported reparse point"; +static const char ntfs_bad_reparse[] = "unsupported reparse tag 0x%08lx"; + /* exact length of target text, without the terminator */ +#define ntfs_bad_reparse_lth (sizeof(ntfs_bad_reparse) + 2) #ifdef FUSE_INTERNAL int drop_privs(void); @@ -719,7 +725,7 @@ static int junction_getattr(ntfs_inode *ni, if (target) stbuf->st_size = strlen(target); else - stbuf->st_size = sizeof(ntfs_bad_reparse) - 1; + stbuf->st_size = ntfs_bad_reparse_lth; stbuf->st_blocks = (ni->allocated_size + 511) >> 9; stbuf->st_mode = S_IFLNK; free(target); @@ -730,6 +736,49 @@ static int junction_getattr(ntfs_inode *ni, return (res); } +static int wsl_getattr(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct stat *stbuf) +{ + dev_t rdev; + int res; + + res = ntfs_reparse_check_wsl(ni, reparse); + if (!res) { + /* switch (reparse->reparse_tag) { */ + if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_AF_UNIX)) { + stbuf->st_mode = S_IFSOCK; + } + else if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_FIFO)) { + stbuf->st_mode = S_IFIFO; + } + else if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_CHR)) { + stbuf->st_mode = S_IFCHR; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + } + else if (le32_eq(reparse->reparse_tag, IO_REPARSE_TAG_LX_BLK)) { + stbuf->st_mode = S_IFBLK; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + } + else { + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + } + /* } */ + } + /* + * If the reparse point is not a valid wsl special file + * we display as a symlink + */ + if (res) { + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + res = 0; + } + return (res); +} + /* * Apply permission masks to st_mode returned by a reparse handler */ @@ -787,6 +836,9 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) } #endif stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + if (ctx->posix_nlink + && le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) + stbuf->st_nlink = ntfs_dir_link_cnt(ni); if ((!le16_andz(ni->mrec->flags, MFT_RECORD_IS_DIRECTORY) || !le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) @@ -801,8 +853,7 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) apply_umask(stbuf); goto ok; } else { - stbuf->st_size = - sizeof(ntfs_bad_reparse) - 1; + stbuf->st_size = ntfs_bad_reparse_lth; stbuf->st_blocks = (ni->allocated_size + 511) >> 9; stbuf->st_mode = S_IFLNK; @@ -824,7 +875,7 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) if (target) stbuf->st_size = strlen(target); else - stbuf->st_size = sizeof(ntfs_bad_reparse) - 1; + stbuf->st_size = ntfs_bad_reparse_lth; stbuf->st_blocks = (ni->allocated_size + 511) >> 9; stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); stbuf->st_mode = S_IFLNK; @@ -849,7 +900,8 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) } stbuf->st_size = ni->data_size; stbuf->st_blocks = ni->allocated_size >> 9; - stbuf->st_nlink = 1; /* Make find(1) work */ + if (!ctx->posix_nlink) + stbuf->st_nlink = 1; /* Make find(1) work */ } } else { /* Regular or Interix (INTX) file. */ @@ -1045,15 +1097,30 @@ static int junction_readlink(ntfs_inode *ni, char **pbuf) { int res; + le32 tag; + int lth; errno = 0; res = 0; *pbuf = ntfs_make_symlink(ni, ctx->abs_mnt_point); if (!*pbuf) { if (errno == EOPNOTSUPP) { - *pbuf = strdup(ntfs_bad_reparse); - if (!*pbuf) - res = -errno; + *pbuf = (char*)ntfs_malloc(ntfs_bad_reparse_lth + 1); + if (*pbuf) { + if (reparse) + tag = reparse->reparse_tag; + else + tag = const_cpu_to_le32(0); + lth = snprintf(*pbuf, ntfs_bad_reparse_lth + 1, + ntfs_bad_reparse, + (long)le32_to_cpu(tag)); + if (lth != ntfs_bad_reparse_lth) { + free(*pbuf); + *pbuf = (char*)NULL; + res = -errno; + } + } else + res = -ENOMEM; } else res = -errno; } @@ -1070,6 +1137,9 @@ static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) ntfs_attr *na = NULL; INTX_FILE *intx_file = NULL; int stream_name_len, res = 0; + REPARSE_POINT *reparse; + le32 tag; + int lth; /* Get inode. */ stream_name_len = ntfs_fuse_parse_path(org_path, &path, &stream_name); @@ -1091,16 +1161,16 @@ static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) #ifndef DISABLE_PLUGINS 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 { + errno = EOPNOTSUPP; + res = -EOPNOTSUPP; } #else /* DISABLE_PLUGINS */ char *target; @@ -1112,11 +1182,22 @@ static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) strncpy(buf,target,buf_size); free(target); } else - if (errno == EOPNOTSUPP) - strcpy(buf,ntfs_bad_reparse); - else - res = -errno; + res = -errno; #endif /* DISABLE_PLUGINS */ + if (res == -EOPNOTSUPP) { + reparse = ntfs_get_reparse_point(ni); + if (reparse) { + tag = reparse->reparse_tag; + free(reparse); + } else + tag = const_cpu_to_le32(0); + lth = snprintf(buf, ntfs_bad_reparse_lth + 1, + ntfs_bad_reparse, + (long)le32_to_cpu(tag)); + res = 0; + if (lth != ntfs_bad_reparse_lth) + res = -errno; + } goto exit; } /* Sanity checks. */ @@ -1199,6 +1280,9 @@ static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, return 0; } else { struct stat st = { .st_ino = MREF(mref) }; +#ifndef DISABLE_PLUGINS + ntfs_inode *ni; +#endif /* DISABLE_PLUGINS */ switch (dt_type) { case NTFS_DT_DIR : @@ -1219,6 +1303,26 @@ static int ntfs_fuse_filler(ntfs_fuse_fill_context_t *fill_ctx, case NTFS_DT_CHR : st.st_mode = S_IFCHR; break; + case NTFS_DT_REPARSE : + st.st_mode = S_IFLNK | 0777; /* default */ +#ifndef DISABLE_PLUGINS + /* get emulated type from plugin if available */ + ni = ntfs_inode_open(ctx->vol, mref); + if (ni && !le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + int res; + + res = CALL_REPARSE_PLUGIN(ni, getattr, &st); + if (!res) + apply_umask(&st); + else + st.st_mode = S_IFLNK; + } + if (ni) + ntfs_inode_close(ni); +#endif /* DISABLE_PLUGINS */ + break; default : /* unexpected types shown as plain files */ case NTFS_DT_REG : st.st_mode = S_IFREG | (0777 & ~ctx->fmask); @@ -1300,6 +1404,17 @@ static int ntfs_fuse_opendir(const char *path, ni,accesstype)) res = -EACCES; } + if (ni->flags & FILE_ATTR_REPARSE_POINT) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + fi->fh = 0; + res = CALL_REPARSE_PLUGIN(ni, opendir, fi); +#else /* DISABLE_PLUGINS */ + res = -EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } if (ntfs_inode_close(ni)) set_fuse_error(&res); } else @@ -1323,9 +1438,22 @@ static int ntfs_fuse_readdir(const char *path, void *buf, ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!ni) return -errno; - if (ntfs_readdir(ni, &pos, &fill_ctx, - (ntfs_filldir_t)ntfs_fuse_filler)) - err = -errno; + + if (!le32_andz(ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + err = CALL_REPARSE_PLUGIN(ni, readdir, &pos, &fill_ctx, + (ntfs_filldir_t)ntfs_fuse_filler, fi); +#else /* DISABLE_PLUGINS */ + err = -EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } else { + if (ntfs_readdir(ni, &pos, &fill_ctx, + (ntfs_filldir_t)ntfs_fuse_filler)) + err = -errno; + } ntfs_fuse_update_times(ni, NTFS_UPDATE_ATIME); if (ntfs_inode_close(ni)) set_fuse_error(&err); @@ -1972,7 +2100,7 @@ static int ntfs_fuse_create(const char *org_path, mode_t typemode, dev_t dev, if (!dir_ni || (dir_ni->mft_no == FILE_Extend)) { free(path); res = -errno; - if (dir_ni->mft_no == FILE_Extend) + if (dir_ni) res = -EPERM; goto exit; } @@ -1989,7 +2117,11 @@ static int ntfs_fuse_create(const char *org_path, mode_t typemode, dev_t dev, perm = (typemode & ~ctx->dmask & 0777) | (dsetgid & S_ISGID); else - perm = typemode & ~ctx->fmask & 0777; + if ((ctx->special_files == NTFS_FILES_WSL) + && S_ISLNK(type)) + perm = typemode | 0777; + else + perm = typemode & ~ctx->fmask & 0777; /* * Try to get a security id available for * file creation (from inheritance or argument). @@ -2013,26 +2145,47 @@ static int ntfs_fuse_create(const char *org_path, mode_t typemode, dev_t dev, 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, + if (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + reparse = (REPARSE_POINT*)NULL; + ops = select_reparse_plugin(ctx, dir_ni, &reparse); + if (ops && ops->create) { + ni = (*ops->create)(dir_ni, reparse, + securid, uname, uname_len, type); + } else { + ni = (ntfs_inode*)NULL; + errno = EOPNOTSUPP; + } + free(reparse); +#else /* DISABLE_PLUGINS */ + errno = EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } else { + 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, + 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; + break; + default: + ni = ntfs_create(dir_ni, securid, + uname, uname_len, type); + break; + } } if (ni) { /* @@ -2255,10 +2408,24 @@ static int ntfs_fuse_link(const char *old_path, const char *new_path) else #endif { - if (ntfs_link(ni, dir_ni, uname, uname_len)) { - res = -errno; - goto exit; - } + if (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(dir_ni, link, + ni, uname, uname_len); +#else /* DISABLE_PLUGINS */ + errno = EOPNOTSUPP; + res = -errno; +#endif /* DISABLE_PLUGINS */ + if (res) + goto exit; + } else + if (ntfs_link(ni, dir_ni, uname, uname_len)) { + res = -errno; + goto exit; + } set_archive(ni); ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME); @@ -2319,7 +2486,7 @@ static int ntfs_fuse_rm(const char *org_path) /* deny unlinking metadata files from $Extend */ if (!dir_ni || (dir_ni->mft_no == FILE_Extend)) { res = -errno; - if (dir_ni->mft_no == FILE_Extend) + if (dir_ni) res = -EPERM; goto exit; } @@ -2330,9 +2497,20 @@ static int ntfs_fuse_rm(const char *org_path) || ntfs_allowed_dir_access(&security, org_path, dir_ni, ni, S_IEXEC + S_IWRITE + S_ISVTX)) { #endif - if (ntfs_delete(ctx->vol, org_path, ni, dir_ni, - uname, uname_len)) - res = -errno; + if (!le32_andz(dir_ni->flags, FILE_ATTR_REPARSE_POINT)) { +#ifndef DISABLE_PLUGINS + const plugin_operations_t *ops; + REPARSE_POINT *reparse; + + res = CALL_REPARSE_PLUGIN(dir_ni, unlink, + org_path, ni, uname, uname_len); +#else /* DISABLE_PLUGINS */ + res = -EOPNOTSUPP; +#endif /* DISABLE_PLUGINS */ + } else + if (ntfs_delete(ctx->vol, org_path, ni, dir_ni, + uname, uname_len)) + res = -errno; /* ntfs_delete() always closes ni and dir_ni */ ni = dir_ni = NULL; #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) @@ -2753,7 +2931,13 @@ static int ntfs_fuse_ioctl(const char *path, if (!ni) return -errno; - ret = ntfs_ioctl(ni, cmd, arg, flags, data); + /* + * Linux defines the request argument of ioctl() to be an + * unsigned long, which fuse 2.x forwards as a signed int into + * which the request sometimes does not fit. + * So we must expand the value and make sure it is not sign-extended. + */ + ret = ntfs_ioctl(ni, (unsigned int)cmd, arg, flags, data); if (ntfs_inode_close (ni)) set_fuse_error(&ret); @@ -2855,7 +3039,7 @@ static ntfs_inode *ntfs_check_access_xattr(struct SECURITY_CONTEXT *security, || !(ctx->secure_flags & (1 << SECURITY_ACL)) || (setting && ctx->inherit)) && foracl) { - if (ctx->silent) + if (ctx->silent && !ctx->security.mapping[MAPUSERS]) errno = 0; else errno = EOPNOTSUPP; @@ -3091,9 +3275,18 @@ exit: return ret; } +#if defined(__APPLE__) || defined(__DARWIN__) +static int ntfs_fuse_getxattr(const char *path, const char *name, + char *value, size_t size, uint32_t position) +#else static int ntfs_fuse_getxattr(const char *path, const char *name, char *value, size_t size) +#endif { +#if !(defined(__APPLE__) || defined(__DARWIN__)) + static const unsigned int position = 0U; +#endif + ntfs_inode *ni; ntfs_inode *dir_ni; ntfs_attr *na = NULL; @@ -3104,6 +3297,15 @@ static int ntfs_fuse_getxattr(const char *path, const char *name, int namespace; struct SECURITY_CONTEXT security; +#if defined(__APPLE__) || defined(__DARWIN__) + /* If the attribute is not a resource fork attribute and the position + * parameter is non-zero, we return with EINVAL as requesting position + * is not permitted for non-resource fork attributes. */ + if (position && strcmp(name, XATTR_RESOURCEFORK_NAME)) { + return -EINVAL; + } +#endif + attr = ntfs_xattr_system_type(name,ctx->vol); if (attr != XATTR_UNMAPPED) { #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) @@ -3176,14 +3378,14 @@ static int ntfs_fuse_getxattr(const char *path, const char *name, /* trusted only readable by root */ if ((namespace == XATTRNS_TRUSTED) && security.uid) - return -ENODATA; + return -NTFS_NOXATTR_ERRNO; #endif ni = ntfs_pathname_to_inode(ctx->vol, NULL, path); if (!ni) return -errno; /* Return with no result for symlinks, fifo, etc. */ if (!user_xattrs_allowed(ctx, ni)) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto exit; } /* otherwise file must be readable */ @@ -3200,7 +3402,7 @@ static int ntfs_fuse_getxattr(const char *path, const char *name, } na = ntfs_attr_open(ni, AT_DATA, lename, lename_len); if (!na) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto exit; } rsize = na->data_size; @@ -3209,9 +3411,10 @@ static int ntfs_fuse_getxattr(const char *path, const char *name, && !le16_andz(na->data_flags, ATTR_IS_ENCRYPTED) && NAttrNonResident(na)) rsize = ((na->data_size + 511) & ~511) + 2; + rsize -= position; if (size) { if (size >= (size_t)rsize) { - res = ntfs_attr_pread(na, 0, rsize, value); + res = ntfs_attr_pread(na, position, rsize, value); if (res != rsize) res = -errno; } else @@ -3227,9 +3430,21 @@ exit: return res; } +#if defined(__APPLE__) || defined(__DARWIN__) +static int ntfs_fuse_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags, + uint32_t position) +#else static int ntfs_fuse_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) +#endif { +#if !(defined(__APPLE__) || defined(__DARWIN__)) + static const unsigned int position = 0U; +#else + BOOL is_resource_fork; +#endif + ntfs_inode *ni; ntfs_inode *dir_ni; ntfs_attr *na = NULL; @@ -3241,6 +3456,16 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, int namespace; struct SECURITY_CONTEXT security; +#if defined(__APPLE__) || defined(__DARWIN__) + /* If the attribute is not a resource fork attribute and the position + * parameter is non-zero, we return with EINVAL as requesting position + * is not permitted for non-resource fork attributes. */ + is_resource_fork = strcmp(name, XATTR_RESOURCEFORK_NAME) ? FALSE : TRUE; + if (position && !is_resource_fork) { + return -EINVAL; + } +#endif + attr = ntfs_xattr_system_type(name,ctx->vol); if (attr != XATTR_UNMAPPED) { #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) @@ -3390,7 +3615,7 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, } if (!na) { if (flags == XATTR_REPLACE) { - res = -ENODATA; + res = -NTFS_NOXATTR_ERRNO; goto exit; } if (ntfs_attr_add(ni, AT_DATA, lename, lename_len, NULL, 0)) { @@ -3406,6 +3631,11 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, res = -errno; goto exit; } +#if defined(__APPLE__) || defined(__DARWIN__) + } else if (is_resource_fork) { + /* In macOS, the resource fork is a special case. It doesn't + * ever shrink (it would have to be removed and re-added). */ +#endif } else { /* currently compressed streams can only be wiped out */ if (ntfs_attr_truncate(na, (s64)0 /* size */)) { @@ -3417,8 +3647,8 @@ static int ntfs_fuse_setxattr(const char *path, const char *name, res = 0; if (size) { do { - part = ntfs_attr_pwrite(na, total, size - total, - &value[total]); + part = ntfs_attr_pwrite(na, position + total, + size - total, &value[total]); if (part > 0) total += part; } while ((part > 0) && (total < size)); @@ -3620,7 +3850,7 @@ static int ntfs_fuse_removexattr(const char *path, const char *name) } if (ntfs_attr_remove(ni, AT_DATA, lename, lename_len)) { if (errno == ENOENT) - errno = ENODATA; + errno = NTFS_NOXATTR_ERRNO; res = -errno; } if (!res) { @@ -3650,10 +3880,26 @@ static void register_internal_reparse_plugins(void) .getattr = junction_getattr, .readlink = junction_readlink, } ; + static const plugin_operations_t wsl_ops = { + .getattr = wsl_getattr, + } ; + register_reparse_plugin(ctx, IO_REPARSE_TAG_MOUNT_POINT, &ops, (void*)NULL); register_reparse_plugin(ctx, IO_REPARSE_TAG_SYMLINK, &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_SYMLINK, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_SYMLINK, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_AF_UNIX, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_FIFO, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_CHR, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_BLK, + &wsl_ops, (void*)NULL); } #endif /* DISABLE_PLUGINS */ @@ -3732,6 +3978,7 @@ static struct fuse_operations ntfs_3g_ops = { #if !KERNELPERMS | (POSIXACLS & !KERNELACLS) .access = ntfs_fuse_access, .opendir = ntfs_fuse_opendir, + .releasedir = ntfs_fuse_release, #endif #ifdef HAVE_SETXATTR .getxattr = ntfs_fuse_getxattr, @@ -4000,13 +4247,28 @@ static struct fuse *mount_fuse(char *parsed_options) if (fuse_opt_add_arg(&args, "") == -1) goto err; + if (ctx->ro) { + char buf[128]; + int len; + + len = snprintf(buf, sizeof(buf), "-ouse_ino,kernel_cache" + ",attr_timeout=%d,entry_timeout=%d", + (int)TIMEOUT_RO, (int)TIMEOUT_RO); + if ((len < 0) + || (len >= (int)sizeof(buf)) + || (fuse_opt_add_arg(&args, buf) == -1)) + goto err; + } else { #if !CACHEING - if (fuse_opt_add_arg(&args, "-ouse_ino,kernel_cache,attr_timeout=0") == -1) - goto err; + if (fuse_opt_add_arg(&args, "-ouse_ino,kernel_cache" + ",attr_timeout=0") == -1) + goto err; #else - if (fuse_opt_add_arg(&args, "-ouse_ino,kernel_cache,attr_timeout=1") == -1) - goto err; + if (fuse_opt_add_arg(&args, "-ouse_ino,kernel_cache" + ",attr_timeout=1") == -1) + goto err; #endif + } if (ctx->debug) if (fuse_opt_add_arg(&args, "-odebug") == -1) goto err; @@ -4123,7 +4385,8 @@ int main(int argc, char *argv[]) else { ctx->abs_mnt_point = (char*)ntfs_malloc(PATH_MAX); if (ctx->abs_mnt_point) { - if (getcwd(ctx->abs_mnt_point, + if ((strlen(opts.mnt_point) < PATH_MAX) + && 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); @@ -4131,6 +4394,9 @@ int main(int argc, char *argv[]) /* Solaris also wants the absolute mount point */ opts.mnt_point = ctx->abs_mnt_point; #endif /* defined(__sun) && defined (__SVR4) */ + } else { + free(ctx->abs_mnt_point); + ctx->abs_mnt_point = (char*)NULL; } } } @@ -4187,16 +4453,22 @@ int main(int argc, char *argv[]) /* Force read-only mount if the device was found read-only */ if (!ctx->ro && NVolReadOnly(ctx->vol)) { + ctx->rw = FALSE; ctx->ro = TRUE; if (ntfs_strinsert(&parsed_options, ",ro")) goto err_out; - } + ntfs_log_info("Could not mount read-write, trying read-only\n"); + } else + if (ctx->rw && ntfs_strinsert(&parsed_options, ",rw")) + 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->vol->abs_mnt_point = ctx->abs_mnt_point; ctx->security.vol = ctx->vol; ctx->vol->secure_flags = ctx->secure_flags; + ctx->vol->special_files = ctx->special_files; #ifdef HAVE_SETXATTR /* extended attributes interface required */ ctx->vol->efs_raw = ctx->efs_raw; #endif /* HAVE_SETXATTR */ diff --git a/src/ntfs-3g_common.c b/src/ntfs-3g_common.c index 841661a2..7f361335 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-2016 Jean-Pierre Andre + * Copyright (c) 2010-2021 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -85,6 +85,7 @@ const struct DEFOPTION optionlist[] = { { "atime", OPT_ATIME, FLGOPT_BOGUS }, { "relatime", OPT_RELATIME, FLGOPT_BOGUS }, { "delay_mtime", OPT_DMTIME, FLGOPT_DECIMAL | FLGOPT_OPTIONAL }, + { "rw", OPT_RW, FLGOPT_BOGUS }, { "fake_rw", OPT_FAKE_RW, FLGOPT_BOGUS }, { "fsname", OPT_FSNAME, FLGOPT_NOSUPPORT }, { "no_def_opts", OPT_NO_DEF_OPTS, FLGOPT_BOGUS }, @@ -125,6 +126,8 @@ const struct DEFOPTION optionlist[] = { { "usermapping", OPT_USERMAPPING, FLGOPT_STRING }, { "xattrmapping", OPT_XATTRMAPPING, FLGOPT_STRING }, { "efs_raw", OPT_EFS_RAW, FLGOPT_BOGUS }, + { "posix_nlink", OPT_POSIX_NLINK, FLGOPT_BOGUS }, + { "special_files", OPT_SPECIAL_FILES, FLGOPT_STRING }, { (const char*)NULL, 0, 0 } /* end marker */ } ; @@ -291,6 +294,9 @@ char *parse_mount_options(ntfs_fuse_context_t *ctx, case OPT_FAKE_RW : ctx->ro = TRUE; break; + case OPT_RW : + ctx->rw = TRUE; + break; case OPT_NOATIME : ctx->atime = ATIME_DISABLED; break; @@ -422,7 +428,14 @@ char *parse_mount_options(ntfs_fuse_context_t *ctx, } break; case OPT_USER_XATTR : +#if defined(__APPLE__) || defined(__DARWIN__) + /* macOS builds use non-namespaced extended + * attributes by default since it matches the + * standard behaviour of macOS filesystems. */ + ctx->streams = NF_STREAMS_INTERFACE_OPENXATTR; +#else ctx->streams = NF_STREAMS_INTERFACE_XATTR; +#endif break; case OPT_NOAUTO : /* Don't pass noauto option to fuse. */ @@ -488,6 +501,20 @@ char *parse_mount_options(ntfs_fuse_context_t *ctx, ctx->efs_raw = TRUE; break; #endif /* HAVE_SETXATTR */ + case OPT_POSIX_NLINK : + ctx->posix_nlink = TRUE; + break; + case OPT_SPECIAL_FILES : + if (!strcmp(val, "interix")) + ctx->special_files = NTFS_FILES_INTERIX; + else if (!strcmp(val, "wsl")) + ctx->special_files = NTFS_FILES_WSL; + else { + ntfs_log_error("Invalid special_files" + " mode.\n"); + goto err_exit; + } + break; case OPT_FSNAME : /* Filesystem name. */ /* * We need this to be able to check whether filesystem @@ -542,6 +569,7 @@ char *parse_mount_options(ntfs_fuse_context_t *ctx, if (ctx->ro) { ctx->secure_flags &= ~(1 << SECURITY_ADDSECURIDS); ctx->hiberfile = FALSE; + ctx->rw = FALSE; } exit: free(options); @@ -796,7 +824,7 @@ const struct plugin_operations *select_reparse_plugin(ntfs_fuse_context_t *ctx, const struct plugin_operations *ops; void *handle; REPARSE_POINT *reparse; - le32 tag; + le32 tag, seltag; plugin_list_t *plugin; plugin_init_t pinit; @@ -804,7 +832,8 @@ const struct plugin_operations *select_reparse_plugin(ntfs_fuse_context_t *ctx, reparse = ntfs_get_reparse_point(ni); if (reparse) { tag = reparse->reparse_tag; - for (plugin=ctx->plugins; plugin && !le32_eq(plugin->tag, tag); + seltag = le32_and(tag, IO_REPARSE_PLUGIN_SELECT); + for (plugin=ctx->plugins; plugin && !le32_eq(plugin->tag, seltag); plugin = plugin->next) { } if (plugin) { ops = plugin->ops; @@ -814,12 +843,12 @@ const struct plugin_operations *select_reparse_plugin(ntfs_fuse_context_t *ctx, snprintf(name,sizeof(name), PLUGIN_DIR "/ntfs-plugin-%08lx.so", - (long)le32_to_cpu(tag)); + (long)le32_to_cpu(seltag)); #else char name[64]; snprintf(name,sizeof(name), "ntfs-plugin-%08lx.so", - (long)le32_to_cpu(tag)); + (long)le32_to_cpu(seltag)); #endif handle = dlopen(name, RTLD_LAZY); if (handle) { @@ -828,13 +857,14 @@ const struct plugin_operations *select_reparse_plugin(ntfs_fuse_context_t *ctx, /* pinit() should set errno if it fails */ ops = (*pinit)(tag); if (ops && register_reparse_plugin(ctx, - tag, ops, handle)) + seltag, ops, handle)) ops = (struct plugin_operations*)NULL; } else errno = ELIBBAD; if (!ops) dlclose(handle); } else { + errno = ELIBACC; if (!(ctx->errors_logged & ERR_PLUGIN)) { ntfs_log_perror( "Could not load plugin %s", diff --git a/src/ntfs-3g_common.h b/src/ntfs-3g_common.h index e2d8790d..4ed256a3 100644 --- a/src/ntfs-3g_common.h +++ b/src/ntfs-3g_common.h @@ -51,6 +51,7 @@ enum { OPT_ATIME, OPT_RELATIME, OPT_DMTIME, + OPT_RW, OPT_FAKE_RW, OPT_FSNAME, OPT_NO_DEF_OPTS, @@ -91,6 +92,8 @@ enum { OPT_USERMAPPING, OPT_XATTRMAPPING, OPT_EFS_RAW, + OPT_POSIX_NLINK, + OPT_SPECIAL_FILES, } ; /* Option flags */ @@ -135,6 +138,7 @@ typedef struct { ntfs_atime_t atime; s64 dmtime; BOOL ro; + BOOL rw; BOOL show_sys_files; BOOL hide_hid_files; BOOL hide_dot_files; @@ -151,6 +155,8 @@ typedef struct { BOOL no_detach; BOOL blkdev; BOOL mounted; + BOOL posix_nlink; + ntfs_volume_special_files special_files; #ifdef HAVE_SETXATTR /* extended attributes interface required */ BOOL efs_raw; #ifdef XATTR_MAPPINGS