From ae459dd7b3adfbc7fb5df9fd5c410e901a4d2e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Pierre=20Andr=C3=A9?= Date: Sat, 12 Apr 2014 09:22:17 +0200 Subject: [PATCH] Implemented an extended attribute to get/set EAs The new extended attribute "system.ntfs_ea" can now be used to get or set the set of EAs of a file or directory. --- include/ntfs-3g/ea.h | 33 ++++ include/ntfs-3g/xattrs.h | 1 + libntfs-3g/Makefile.am | 1 + libntfs-3g/ea.c | 402 +++++++++++++++++++++++++++++++++++++++ libntfs-3g/reparse.c | 8 +- libntfs-3g/xattrs.c | 15 +- ntfsprogs/ntfsinfo.c | 37 +++- 7 files changed, 484 insertions(+), 13 deletions(-) create mode 100644 include/ntfs-3g/ea.h create mode 100644 libntfs-3g/ea.c diff --git a/include/ntfs-3g/ea.h b/include/ntfs-3g/ea.h new file mode 100644 index 00000000..1936aab4 --- /dev/null +++ b/include/ntfs-3g/ea.h @@ -0,0 +1,33 @@ +/* + * + * Copyright (c) 2014 Jean-Pierre Andre + * + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the NTFS-3G + * distribution in the file COPYING); if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef EA_H +#define EA_H + +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); + +int ntfs_remove_ntfs_ea(ntfs_inode *ni); + +#endif /* EA_H */ diff --git a/include/ntfs-3g/xattrs.h b/include/ntfs-3g/xattrs.h index 51583114..d4e43a3e 100644 --- a/include/ntfs-3g/xattrs.h +++ b/include/ntfs-3g/xattrs.h @@ -39,6 +39,7 @@ enum SYSTEMXATTRS { XATTR_NTFS_TIMES_BE, XATTR_NTFS_CRTIME, XATTR_NTFS_CRTIME_BE, + XATTR_NTFS_EA, XATTR_POSIX_ACC, XATTR_POSIX_DEF } ; diff --git a/libntfs-3g/Makefile.am b/libntfs-3g/Makefile.am index 045ca52d..47337b2e 100644 --- a/libntfs-3g/Makefile.am +++ b/libntfs-3g/Makefile.am @@ -27,6 +27,7 @@ libntfs_3g_la_SOURCES = \ debug.c \ device.c \ dir.c \ + ea.c \ efs.c \ index.c \ inode.c \ diff --git a/libntfs-3g/ea.c b/libntfs-3g/ea.c new file mode 100644 index 00000000..d07e111b --- /dev/null +++ b/libntfs-3g/ea.c @@ -0,0 +1,402 @@ +/** + * ea.c - Processing of EA's + * + * This module is part of ntfs-3g library + * + * Copyright (c) 2014 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 + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program/include file is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the NTFS-3G + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SETXATTR /* extended attributes support required */ + +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#ifdef HAVE_SETXATTR +#include +#endif + +#include "types.h" +#include "param.h" +#include "layout.h" +#include "attrib.h" +#include "index.h" +#include "dir.h" +#include "ea.h" +#include "misc.h" +#include "logging.h" + +/* + * Create a needed attribute (EA or EA_INFORMATION) + * + * Returns 0 if successful, + * -1 otherwise, with errno indicating why it failed. + */ + +static int ntfs_need_ea(ntfs_inode *ni, ATTR_TYPES type, int size, int flags) +{ + u8 dummy; + int res; + + res = 0; + if (!ntfs_attr_exist(ni,type, AT_UNNAMED,0)) { + if (!(flags & XATTR_REPLACE)) { + /* + * no needed attribute : add one, + * apparently, this does not feed the new value in + * Note : NTFS version must be >= 3 + */ + if (ni->vol->major_ver >= 3) { + res = ntfs_attr_add(ni, type, + AT_UNNAMED,0,&dummy,(s64)size); + if (!res) { + NInoFileNameSetDirty(ni); + } + NInoSetDirty(ni); + } else { + errno = EOPNOTSUPP; + res = -1; + } + } else { + errno = ENODATA; + res = -1; + } + } + return (res); +} + +/* + * Restore the old EA_INFORMATION or delete the current one, + * when EA cannot be updated. + * + * As this is used in the context of some other error, the caller + * is responsible for returning the proper error, and errno is + * left unchanged. + * Only double errors are logged here. + */ + +static void restore_ea_info(ntfs_attr *nai, const EA_INFORMATION *old_ea_info) +{ + s64 written; + int olderrno; + + olderrno = errno; + if (old_ea_info) { + written = ntfs_attr_pwrite(nai, 0, sizeof(EA_INFORMATION), + old_ea_info); + if ((size_t)written != sizeof(EA_INFORMATION)) { + ntfs_log_error("Could not restore the EA_INFORMATION," + " possible inconsistency in inode %lld\n", + (long long)nai->ni->mft_no); + } + } else { + if (ntfs_attr_rm(nai)) { + ntfs_log_error("Could not delete the EA_INFORMATION," + " possible inconsistency in inode %lld\n", + (long long)nai->ni->mft_no); + } + } + errno = olderrno; +} + +/* + * Update both EA and EA_INFORMATION + */ + +static int ntfs_update_ea(ntfs_inode *ni, const char *value, size_t size, + const EA_INFORMATION *ea_info, + const EA_INFORMATION *old_ea_info) +{ + ntfs_attr *na; + ntfs_attr *nai; + int res; + + res = 0; + nai = ntfs_attr_open(ni, AT_EA_INFORMATION, AT_UNNAMED, 0); + if (nai) { + na = ntfs_attr_open(ni, AT_EA, AT_UNNAMED, 0); + if (na) { + /* + * Set EA_INFORMATION first, it is easier to + * restore the old value, if setting EA fails. + */ + if (ntfs_attr_pwrite(nai, 0, sizeof(EA_INFORMATION), + ea_info) + != (s64)sizeof(EA_INFORMATION)) { + res = -errno; + } else { + if (((na->data_size > (s64)size) + && ntfs_attr_truncate(na, size)) + || (ntfs_attr_pwrite(na, 0, size, value) + != (s64)size)) { + res = -errno; + if (old_ea_info) + restore_ea_info(nai, + old_ea_info); + } + } + ntfs_attr_close(na); + } + ntfs_attr_close(nai); + } else { + res = -errno; + } + return (res); +} + +/* + * Return the existing EA + * + * The EA_INFORMATION is not examined and the consistency of the + * existing EA is not checked. + * + * If successful, the full attribute is returned unchanged + * and its size is returned. + * If the designated buffer is too small, the needed size is + * returned, and the buffer is left unchanged. + * If there is an error, a negative value is returned and errno + * is set according to the error. + */ + +int ntfs_get_ntfs_ea(ntfs_inode *ni, char *value, size_t size) +{ + s64 ea_size; + void *ea_buf; + int res = 0; + + if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) { + ea_buf = ntfs_attr_readall(ni, AT_EA, (ntfschar*)NULL, 0, + &ea_size); + if (ea_buf) { + if (value && (ea_size <= (s64)size)) + memcpy(value, ea_buf, ea_size); + free(ea_buf); + res = ea_size; + } else { + ntfs_log_error("Failed to read EA from inode %lld\n", + (long long)ni->mft_no); + errno = ENODATA; + res = -errno; + } + } else { + errno = ENODATA; + res = -errno; + } + return (res); +} + +/* + * Set a new EA, and set EA_INFORMATION accordingly + * + * This is roughly the same as ZwSetEaFile() on Windows, however + * the "offset to next" of the last EA should not be cleared. + * + * Consistency of the new EA is first checked. + * + * EA_INFORMATION is set first, and it is restored to its former + * state if setting EA fails. + * + * Returns 0 if successful + * a negative value if an error occurred. + */ + +int ntfs_set_ntfs_ea(ntfs_inode *ni, const char *value, size_t size, int flags) +{ + EA_INFORMATION ea_info; + EA_INFORMATION *old_ea_info; + s64 old_ea_size; + int res; + size_t offs; + size_t nextoffs; + BOOL ok; + int ea_count; + int ea_packed; + const EA_ATTR *p_ea; + + res = -1; + if (value && (size > 0)) { + /* do consistency checks */ + offs = 0; + ok = TRUE; + ea_count = 0; + ea_packed = 0; + nextoffs = 0; + while (ok && (offs < size)) { + p_ea = (const EA_ATTR*)&value[offs]; + nextoffs = offs + le32_to_cpu(p_ea->next_entry_offset); + /* null offset to next not allowed */ + ok = (nextoffs > offs) + && (nextoffs <= size) + && !(nextoffs & 3) + && p_ea->name_length + /* zero sized value are allowed */ + && ((offs + offsetof(EA_ATTR,name) + + p_ea->name_length + 1 + + le16_to_cpu(p_ea->value_length)) + <= nextoffs) + && ((offs + offsetof(EA_ATTR,name) + + p_ea->name_length + 1 + + le16_to_cpu(p_ea->value_length)) + >= (nextoffs - 3)) + && !p_ea->name[p_ea->name_length]; + /* name not checked, as chkdsk accepts any chars */ + if (ok) { + if (p_ea->flags & NEED_EA) + ea_count++; + /* + * Assume ea_packed includes : + * 4 bytes for header (flags and lengths) + * + name length + 1 + * + value length + */ + ea_packed += 5 + p_ea->name_length + + le16_to_cpu(p_ea->value_length); + offs = nextoffs; + } + } + /* + * 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. + */ + if (ok + && !ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED,0)) { + 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); + + old_ea_size = 0; + old_ea_info = NULL; + /* Try to save the old EA_INFORMATION */ + if (ntfs_attr_exist(ni, AT_EA_INFORMATION, + AT_UNNAMED, 0)) { + old_ea_info = ntfs_attr_readall(ni, + AT_EA_INFORMATION, + (ntfschar*)NULL, 0, &old_ea_size); + } + /* + * no EA or EA_INFORMATION : add them + */ + if (!ntfs_need_ea(ni, AT_EA_INFORMATION, + sizeof(EA_INFORMATION), flags) + && !ntfs_need_ea(ni, AT_EA, 0, flags)) { + res = ntfs_update_ea(ni, value, size, + &ea_info, old_ea_info); + } else { + res = -errno; + } + if (old_ea_info) + free(old_ea_info); + } else { + errno = EINVAL; + res = -errno; + } + } else { + errno = EINVAL; + res = -errno; + } + return (res); +} + +/* + * Remove the EA (including EA_INFORMATION) + * + * EA_INFORMATION is removed first, and it is restored to its former + * state if removing EA fails. + * + * Returns 0, or -1 if there is a problem + */ + +int ntfs_remove_ntfs_ea(ntfs_inode *ni) +{ + EA_INFORMATION *old_ea_info; + s64 old_ea_size; + int res; + ntfs_attr *na; + ntfs_attr *nai; + + res = 0; + if (ni) { + /* + * open and delete the EA_INFORMATION and the EA + */ + nai = ntfs_attr_open(ni, AT_EA_INFORMATION, AT_UNNAMED, 0); + if (nai) { + na = ntfs_attr_open(ni, AT_EA, AT_UNNAMED, 0); + if (na) { + /* Try to save the old EA_INFORMATION */ + old_ea_info = ntfs_attr_readall(ni, + AT_EA_INFORMATION, + (ntfschar*)NULL, 0, &old_ea_size); + res = ntfs_attr_rm(na); + NInoFileNameSetDirty(ni); + if (!res) { + res = ntfs_attr_rm(nai); + if (res && old_ea_info) { + /* + * Failed to remove the EA, try to + * restore the EA_INFORMATION + */ + restore_ea_info(nai, + old_ea_info); + } + } else { + ntfs_log_error("Failed to remove the" + " EA_INFORMATION from inode %lld\n", + (long long)ni->mft_no); + } + free(old_ea_info); + ntfs_attr_close(na); + } else { + /* EA_INFORMATION present, but no EA */ + res = ntfs_attr_rm(nai); + NInoFileNameSetDirty(ni); + } + ntfs_attr_close(nai); + } else { + errno = ENODATA; + res = -1; + } + NInoSetDirty(ni); + } else { + errno = EINVAL; + res = -1; + } + return (res ? -1 : 0); +} + +#endif /* HAVE_SETXATTR */ diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index cdabadd2..7b96902c 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-2013 Jean-Pierre Andre + * Copyright (c) 2008-2014 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 @@ -1117,7 +1117,11 @@ int ntfs_set_ntfs_reparse_data(ntfs_inode *ni, ntfs_index_context *xr; res = 0; - if (ni && valid_reparse_data(ni, (const REPARSE_POINT*)value, size)) { + /* 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)) { xr = open_reparse_index(ni->vol); if (xr) { if (!ntfs_attr_exist(ni,AT_REPARSE_POINT, diff --git a/libntfs-3g/xattrs.c b/libntfs-3g/xattrs.c index 5be2c06a..6da81467 100644 --- a/libntfs-3g/xattrs.c +++ b/libntfs-3g/xattrs.c @@ -1,7 +1,7 @@ /** * xattrs.c : common functions to deal with system extended attributes * - * Copyright (c) 2010 Jean-Pierre Andre + * Copyright (c) 2010-2014 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 @@ -55,6 +55,7 @@ #include "efs.h" #include "reparse.h" #include "object_id.h" +#include "ea.h" #include "misc.h" #include "logging.h" #include "xattrs.h" @@ -97,6 +98,7 @@ static const char nf_ns_xattr_times[] = "system.ntfs_times"; static const char nf_ns_xattr_times_be[] = "system.ntfs_times_be"; static const char nf_ns_xattr_crtime[] = "system.ntfs_crtime"; static const char nf_ns_xattr_crtime_be[] = "system.ntfs_crtime_be"; +static const char nf_ns_xattr_ea[] = "system.ntfs_ea"; static const char nf_ns_xattr_posix_access[] = "system.posix_acl_access"; static const char nf_ns_xattr_posix_default[] = "system.posix_acl_default"; @@ -119,6 +121,7 @@ static struct XATTRNAME nf_ns_xattr_names[] = { { XATTR_NTFS_TIMES_BE, nf_ns_xattr_times_be }, { XATTR_NTFS_CRTIME, nf_ns_xattr_crtime }, { XATTR_NTFS_CRTIME_BE, nf_ns_xattr_crtime_be }, + { XATTR_NTFS_EA, nf_ns_xattr_ea }, { XATTR_POSIX_ACC, nf_ns_xattr_posix_access }, { XATTR_POSIX_DEF, nf_ns_xattr_posix_default }, { XATTR_UNMAPPED, (char*)NULL } /* terminator */ @@ -473,7 +476,6 @@ void ntfs_xattr_free_mapping(struct XATTRMAPPING *mapping) #endif /* XATTR_MAPPINGS */ - int ntfs_xattr_system_getxattr(struct SECURITY_CONTEXT *scx, enum SYSTEMXATTRS attr, ntfs_inode *ni, ntfs_inode *dir_ni, @@ -589,6 +591,9 @@ int ntfs_xattr_system_getxattr(struct SECURITY_CONTEXT *scx, if ((res >= (int)sizeof(u64)) && value) fix_big_endian(value,sizeof(u64)); break; + case XATTR_NTFS_EA : + res = ntfs_get_ntfs_ea(ni, value, size); + break; default : errno = EOPNOTSUPP; res = -errno; @@ -712,6 +717,9 @@ int ntfs_xattr_system_setxattr(struct SECURITY_CONTEXT *scx, } else res = ntfs_inode_set_times(ni, value, size, flags); break; + case XATTR_NTFS_EA : + res = ntfs_set_ntfs_ea(ni, value, size, flags); + break; default : errno = EOPNOTSUPP; res = -errno; @@ -780,6 +788,9 @@ int ntfs_xattr_system_removexattr(struct SECURITY_CONTEXT *scx, } else res = -errno; break; + case XATTR_NTFS_EA : + res = ntfs_remove_ntfs_ea(ni); + break; default : errno = EOPNOTSUPP; res = -errno; diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index d8a540b6..5aac9a68 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -1979,9 +1979,12 @@ static void ntfs_dump_attr_ea_information(ATTR_RECORD *attr) */ static void ntfs_dump_attr_ea(ATTR_RECORD *attr, ntfs_volume *vol) { - EA_ATTR *ea; + const EA_ATTR *ea; + const u8 *pvalue; u8 *buf = NULL; - le32 *pval; + const le32 *pval; + int offset; + int cnt; s64 data_size; if (attr->non_resident) { @@ -2019,6 +2022,7 @@ static void ntfs_dump_attr_ea(ATTR_RECORD *attr, ntfs_volume *vol) return; ea = (EA_ATTR*)((u8*)attr + le16_to_cpu(attr->value_offset)); } + offset = 0; while (1) { printf("\n\tEA flags:\t\t "); if (ea->flags) { @@ -2035,21 +2039,36 @@ static void ntfs_dump_attr_ea(ATTR_RECORD *attr, ntfs_volume *vol) printf("\tValue length:\t %d (0x%x)\n", (unsigned)le16_to_cpu(ea->value_length), (unsigned)le16_to_cpu(ea->value_length)); + /* Name expected to be null terminated ? */ printf("\tName:\t\t '%s'\n", ea->name); printf("\tValue:\t\t "); if (ea->name_length == 11 && !strncmp((const char*)"SETFILEBITS", (const char*)ea->name, 11)) { - pval = (le32*)(ea->value + ea->name_length + 1); + pval = (const le32*)(ea->value + ea->name_length + 1); printf("0%lo\n", (unsigned long)le32_to_cpu(*pval)); + } else { + /* No alignment for value */ + pvalue = ea->value + ea->name_length + 1; + /* Hex show a maximum of 32 bytes */ + cnt = le16_to_cpu(ea->value_length); + printf(cnt ? "0x" : "(NONE)"); + if (cnt > 32) + cnt = 32; + while (cnt-- > 0) + printf("%02x",*pvalue++); + if (le16_to_cpu(ea->value_length) > 32) + printf("...\n"); + else + printf("\n"); + } + if (ea->next_entry_offset) { + offset += le32_to_cpu(ea->next_entry_offset); + ea = (const EA_ATTR*)((const u8*)ea + + le32_to_cpu(ea->next_entry_offset)); } else - printf("'%s'\n", ea->value + ea->name_length + 1); - if (ea->next_entry_offset) - ea = (EA_ATTR*)((u8*)ea + - le32_to_cpu(ea->next_entry_offset)); - else break; - if ((u8*)ea - buf >= data_size) + if (offset >= data_size) break; } free(buf);