| /** |
| * 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 <stdio.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| |
| #ifdef HAVE_SETXATTR |
| #include <sys/xattr.h> |
| #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 */ |