summaryrefslogtreecommitdiff
Diffstat
-rwxr-xr-xAUTHORS23
-rwxr-xr-xAndroid.mk89
-rwxr-xr-xAndroid.mk.bak89
-rwxr-xr-xCOPYING340
-rwxr-xr-xCOPYING.LIB482
-rwxr-xr-xCREDITS41
-rwxr-xr-xChangeLog7
-rwxr-xr-xINSTALL237
-rwxr-xr-xMakefile715
-rwxr-xr-xMakefile.am66
-rwxr-xr-xMakefile.in722
-rwxr-xr-xNEWS5
-rwxr-xr-xREADME68
-rwxr-xr-xaclocal.m47723
-rwxr-xr-xautogen.sh22
-rwxr-xr-xcompile142
-rwxr-xr-xconfig.guess1536
-rwxr-xr-xconfig.h361
-rwxr-xr-xconfig.h.in350
-rwxr-xr-xconfig.log3290
-rwxr-xr-xconfig.status1220
-rwxr-xr-xconfig.sub1658
-rwxr-xr-xconfigure25302
-rwxr-xr-xconfigure.ac441
-rwxr-xr-xdepcomp589
-rwxr-xr-xinclude/Makefile.am4
-rwxr-xr-xinclude/Makefile.in503
-rwxr-xr-xinclude/fuse-lite/Makefile.am10
-rwxr-xr-xinclude/fuse-lite/Makefile.in400
-rwxr-xr-xinclude/fuse-lite/fuse.h654
-rwxr-xr-xinclude/fuse-lite/fuse_common.h193
-rwxr-xr-xinclude/fuse-lite/fuse_kernel.h422
-rwxr-xr-xinclude/fuse-lite/fuse_lowlevel.h1375
-rwxr-xr-xinclude/fuse-lite/fuse_lowlevel_compat.h16
-rwxr-xr-xinclude/fuse-lite/fuse_opt.h261
-rwxr-xr-xinclude/ntfs-3g/Makefile.am45
-rwxr-xr-xinclude/ntfs-3g/Makefile.in471
-rwxr-xr-xinclude/ntfs-3g/acls.h199
-rwxr-xr-xinclude/ntfs-3g/attrib.h358
-rwxr-xr-xinclude/ntfs-3g/attrlist.h51
-rwxr-xr-xinclude/ntfs-3g/bitmap.h96
-rwxr-xr-xinclude/ntfs-3g/bootsect.h42
-rwxr-xr-xinclude/ntfs-3g/cache.h115
-rwxr-xr-xinclude/ntfs-3g/collate.h34
-rwxr-xr-xinclude/ntfs-3g/compat.h69
-rwxr-xr-xinclude/ntfs-3g/compress.h39
-rwxr-xr-xinclude/ntfs-3g/debug.h47
-rwxr-xr-xinclude/ntfs-3g/device.h128
-rwxr-xr-xinclude/ntfs-3g/device_io.h77
-rwxr-xr-xinclude/ntfs-3g/dir.h128
-rwxr-xr-xinclude/ntfs-3g/efs.h30
-rwxr-xr-xinclude/ntfs-3g/endians.h203
-rwxr-xr-xinclude/ntfs-3g/index.h167
-rwxr-xr-xinclude/ntfs-3g/inode.h225
-rwxr-xr-xinclude/ntfs-3g/layout.h2661
-rwxr-xr-xinclude/ntfs-3g/lcnalloc.h50
-rwxr-xr-xinclude/ntfs-3g/logfile.h394
-rwxr-xr-xinclude/ntfs-3g/logging.h118
-rwxr-xr-xinclude/ntfs-3g/mft.h132
-rwxr-xr-xinclude/ntfs-3g/misc.h30
-rwxr-xr-xinclude/ntfs-3g/mst.h34
-rwxr-xr-xinclude/ntfs-3g/ntfstime.h131
-rwxr-xr-xinclude/ntfs-3g/object_id.h35
-rwxr-xr-xinclude/ntfs-3g/param.h63
-rwxr-xr-xinclude/ntfs-3g/reparse.h39
-rwxr-xr-xinclude/ntfs-3g/runlist.h89
-rwxr-xr-xinclude/ntfs-3g/security.h353
-rwxr-xr-xinclude/ntfs-3g/support.h85
-rwxr-xr-xinclude/ntfs-3g/types.h124
-rwxr-xr-xinclude/ntfs-3g/unistr.h116
-rwxr-xr-xinclude/ntfs-3g/volume.h277
-rwxr-xr-xinstall-sh519
-rwxr-xr-xlibfuse-lite/Makefile.am28
-rwxr-xr-xlibfuse-lite/Makefile.in572
-rwxr-xr-xlibfuse-lite/fuse.c2789
-rwxr-xr-xlibfuse-lite/fuse_i.h25
-rwxr-xr-xlibfuse-lite/fuse_kern_chan.c96
-rwxr-xr-xlibfuse-lite/fuse_loop.c40
-rwxr-xr-xlibfuse-lite/fuse_lowlevel.c1395
-rwxr-xr-xlibfuse-lite/fuse_misc.h106
-rwxr-xr-xlibfuse-lite/fuse_opt.c368
-rwxr-xr-xlibfuse-lite/fuse_session.c183
-rwxr-xr-xlibfuse-lite/fuse_signals.c73
-rwxr-xr-xlibfuse-lite/fusermount.c772
-rwxr-xr-xlibfuse-lite/helper.c40
-rwxr-xr-xlibfuse-lite/mount.c256
-rwxr-xr-xlibfuse-lite/mount_util.c219
-rwxr-xr-xlibfuse-lite/mount_util.h22
-rwxr-xr-xlibntfs-3g/Makefile.am79
-rwxr-xr-xlibntfs-3g/Makefile.in841
-rwxr-xr-xlibntfs-3g/acls.c4296
-rwxr-xr-xlibntfs-3g/attrib.c5913
-rwxr-xr-xlibntfs-3g/attrlist.c314
-rwxr-xr-xlibntfs-3g/bitmap.c300
-rwxr-xr-xlibntfs-3g/bootsect.c285
-rwxr-xr-xlibntfs-3g/cache.c609
-rwxr-xr-xlibntfs-3g/collate.c271
-rwxr-xr-xlibntfs-3g/compat.c250
-rwxr-xr-xlibntfs-3g/compress.c1431
-rwxr-xr-xlibntfs-3g/debug.c79
-rwxr-xr-xlibntfs-3g/device.c730
-rwxr-xr-xlibntfs-3g/dir.c2582
-rwxr-xr-xlibntfs-3g/efs.c346
-rwxr-xr-xlibntfs-3g/index.c2063
-rwxr-xr-xlibntfs-3g/inode.c1566
-rwxr-xr-xlibntfs-3g/lcnalloc.c735
-rwxr-xr-xlibntfs-3g/libntfs-3g.pc.in10
-rwxr-xr-xlibntfs-3g/libntfs-3g.script.so.in2
-rwxr-xr-xlibntfs-3g/logfile.c737
-rwxr-xr-xlibntfs-3g/logging.c613
-rwxr-xr-xlibntfs-3g/mft.c1909
-rwxr-xr-xlibntfs-3g/misc.c61
-rwxr-xr-xlibntfs-3g/mst.c231
-rwxr-xr-xlibntfs-3g/object_id.c637
-rwxr-xr-xlibntfs-3g/reparse.c1222
-rwxr-xr-xlibntfs-3g/runlist.c2166
-rwxr-xr-xlibntfs-3g/security.c5167
-rwxr-xr-xlibntfs-3g/unistr.c1321
-rwxr-xr-xlibntfs-3g/unix_io.c386
-rwxr-xr-xlibntfs-3g/volume.c1663
-rwxr-xr-xlibntfs-3g/win32_io.c1477
-rwxr-xr-xlibtool7621
-rwxr-xr-xltmain.sh6956
-rwxr-xr-xm4/.keep0
-rwxr-xr-xmissing367
-rwxr-xr-xprog.IAB1071
-rwxr-xr-xprog.IAD5
-rwxr-xr-xprog.IMB466
-rwxr-xr-xprog.IMD2
-rwxr-xr-xprog.PFI2
-rwxr-xr-xprog.PO1
-rwxr-xr-xprog.PR14
-rwxr-xr-xprog.PRI219
-rwxr-xr-xprog.PS979
-rwxr-xr-xprog.SearchResults3
-rwxr-xr-xprog.WK39
-rwxr-xr-xsrc/lowntfs-3g.c4492
-rwxr-xr-xsrc/ntfs-3g.8.in349
-rwxr-xr-xsrc/ntfs-3g.c4461
-rwxr-xr-xsrc/ntfs-3g.probe.8.in81
-rwxr-xr-xsrc/ntfs-3g.probe.c163
-rwxr-xr-xsrc/ntfs-3g.secaudit.8.in171
-rwxr-xr-xsrc/ntfs-3g.usermap.8.in96
-rwxr-xr-xsrc/secaudit.c7176
-rwxr-xr-xsrc/secaudit.h731
-rwxr-xr-xsrc/test.c88
-rwxr-xr-xsrc/usermap.c1356
-rwxr-xr-xstamp-h11
148 files changed, 141286 insertions, 0 deletions
diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c
new file mode 100755
index 0000000..12ea369
--- a/dev/null
+++ b/libntfs-3g/attrib.c
@@ -0,0 +1,5913 @@
+/**
+ * attrib.c - Attribute handling code. Originated from the Linux-NTFS project.
+ *
+ * Copyright (c) 2000-2010 Anton Altaparmakov
+ * Copyright (c) 2002-2005 Richard Russon
+ * Copyright (c) 2002-2008 Szabolcs Szakacsits
+ * Copyright (c) 2004-2007 Yura Pakhuchiy
+ * Copyright (c) 2007-2010 Jean-Pierre Andre
+ * Copyright (c) 2010 Erik Larsson
+ *
+ * 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_STDIO_H
+#include <stdio.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include "param.h"
+#include "compat.h"
+#include "attrib.h"
+#include "attrlist.h"
+#include "device.h"
+#include "mft.h"
+#include "debug.h"
+#include "mst.h"
+#include "volume.h"
+#include "types.h"
+#include "layout.h"
+#include "inode.h"
+#include "runlist.h"
+#include "lcnalloc.h"
+#include "dir.h"
+#include "compress.h"
+#include "bitmap.h"
+#include "logging.h"
+#include "misc.h"
+#include "efs.h"
+
+#define STANDARD_COMPRESSION_UNIT 4
+#define MAX_COMPRESSION_CLUSTER_SIZE 4096
+
+ntfschar AT_UNNAMED[] = { const_cpu_to_le16('\0') };
+ntfschar STREAM_SDS[] = { const_cpu_to_le16('$'),
+ const_cpu_to_le16('S'),
+ const_cpu_to_le16('D'),
+ const_cpu_to_le16('S'),
+ const_cpu_to_le16('\0') };
+
+ntfschar TXF_DATA[] = { const_cpu_to_le16('$'),
+ const_cpu_to_le16('T'),
+ const_cpu_to_le16('X'),
+ const_cpu_to_le16('F'),
+ const_cpu_to_le16('_'),
+ const_cpu_to_le16('D'),
+ const_cpu_to_le16('A'),
+ const_cpu_to_le16('T'),
+ const_cpu_to_le16('A'),
+ const_cpu_to_le16('\0') };
+
+static int NAttrFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
+{
+ if (na->type == AT_DATA && na->name == AT_UNNAMED)
+ return (na->ni->flags & flag);
+ return 0;
+}
+
+static void NAttrSetFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
+{
+ if (na->type == AT_DATA && na->name == AT_UNNAMED)
+ na->ni->flags |= flag;
+ else
+ ntfs_log_trace("Denied setting flag %d for not unnamed data "
+ "attribute\n", flag);
+}
+
+static void NAttrClearFlag(ntfs_attr *na, FILE_ATTR_FLAGS flag)
+{
+ if (na->type == AT_DATA && na->name == AT_UNNAMED)
+ na->ni->flags &= ~flag;
+}
+
+#define GenNAttrIno(func_name, flag) \
+int NAttr##func_name(ntfs_attr *na) { return NAttrFlag (na, flag); } \
+void NAttrSet##func_name(ntfs_attr *na) { NAttrSetFlag (na, flag); } \
+void NAttrClear##func_name(ntfs_attr *na){ NAttrClearFlag(na, flag); }
+
+GenNAttrIno(Compressed, FILE_ATTR_COMPRESSED)
+GenNAttrIno(Encrypted, FILE_ATTR_ENCRYPTED)
+GenNAttrIno(Sparse, FILE_ATTR_SPARSE_FILE)
+
+/**
+ * ntfs_get_attribute_value_length - Find the length of an attribute
+ * @a:
+ *
+ * Description...
+ *
+ * Returns:
+ */
+s64 ntfs_get_attribute_value_length(const ATTR_RECORD *a)
+{
+ if (!a) {
+ errno = EINVAL;
+ return 0;
+ }
+ errno = 0;
+ if (a->non_resident)
+ return sle64_to_cpu(a->data_size);
+
+ return (s64)le32_to_cpu(a->value_length);
+}
+
+/**
+ * ntfs_get_attribute_value - Get a copy of an attribute
+ * @vol:
+ * @a:
+ * @b:
+ *
+ * Description...
+ *
+ * Returns:
+ */
+s64 ntfs_get_attribute_value(const ntfs_volume *vol,
+ const ATTR_RECORD *a, u8 *b)
+{
+ runlist *rl;
+ s64 total, r;
+ int i;
+
+ /* Sanity checks. */
+ if (!vol || !a || !b) {
+ errno = EINVAL;
+ return 0;
+ }
+ /* Complex attribute? */
+ /*
+ * Ignore the flags in case they are not zero for an attribute list
+ * attribute. Windows does not complain about invalid flags and chkdsk
+ * does not detect or fix them so we need to cope with it, too.
+ */
+ if (a->type != AT_ATTRIBUTE_LIST && a->flags) {
+ ntfs_log_error("Non-zero (%04x) attribute flags. Cannot handle "
+ "this yet.\n", le16_to_cpu(a->flags));
+ errno = EOPNOTSUPP;
+ return 0;
+ }
+ if (!a->non_resident) {
+ /* Attribute is resident. */
+
+ /* Sanity check. */
+ if (le32_to_cpu(a->value_length) + le16_to_cpu(a->value_offset)
+ > le32_to_cpu(a->length)) {
+ return 0;
+ }
+
+ memcpy(b, (const char*)a + le16_to_cpu(a->value_offset),
+ le32_to_cpu(a->value_length));
+ errno = 0;
+ return (s64)le32_to_cpu(a->value_length);
+ }
+
+ /* Attribute is not resident. */
+
+ /* If no data, return 0. */
+ if (!(a->data_size)) {
+ errno = 0;
+ return 0;
+ }
+ /*
+ * FIXME: What about attribute lists?!? (AIA)
+ */
+ /* Decompress the mapping pairs array into a runlist. */
+ rl = ntfs_mapping_pairs_decompress(vol, a, NULL);
+ if (!rl) {
+ errno = EINVAL;
+ return 0;
+ }
+ /*
+ * FIXED: We were overflowing here in a nasty fashion when we
+ * reach the last cluster in the runlist as the buffer will
+ * only be big enough to hold data_size bytes while we are
+ * reading in allocated_size bytes which is usually larger
+ * than data_size, since the actual data is unlikely to have a
+ * size equal to a multiple of the cluster size!
+ * FIXED2: We were also overflowing here in the same fashion
+ * when the data_size was more than one run smaller than the
+ * allocated size which happens with Windows XP sometimes.
+ */
+ /* Now load all clusters in the runlist into b. */
+ for (i = 0, total = 0; rl[i].length; i++) {
+ if (total + (rl[i].length << vol->cluster_size_bits) >=
+ sle64_to_cpu(a->data_size)) {
+ unsigned char *intbuf = NULL;
+ /*
+ * We have reached the last run so we were going to
+ * overflow when executing the ntfs_pread() which is
+ * BAAAAAAAD!
+ * Temporary fix:
+ * Allocate a new buffer with size:
+ * rl[i].length << vol->cluster_size_bits, do the
+ * read into our buffer, then memcpy the correct
+ * amount of data into the caller supplied buffer,
+ * free our buffer, and continue.
+ * We have reached the end of data size so we were
+ * going to overflow in the same fashion.
+ * Temporary fix: same as above.
+ */
+ intbuf = ntfs_malloc(rl[i].length << vol->cluster_size_bits);
+ if (!intbuf) {
+ free(rl);
+ return 0;
+ }
+ /*
+ * FIXME: If compressed file: Only read if lcn != -1.
+ * Otherwise, we are dealing with a sparse run and we
+ * just memset the user buffer to 0 for the length of
+ * the run, which should be 16 (= compression unit
+ * size).
+ * FIXME: Really only when file is compressed, or can
+ * we have sparse runs in uncompressed files as well?
+ * - Yes we can, in sparse files! But not necessarily
+ * size of 16, just run length.
+ */
+ r = ntfs_pread(vol->dev, rl[i].lcn <<
+ vol->cluster_size_bits, rl[i].length <<
+ vol->cluster_size_bits, intbuf);
+ if (r != rl[i].length << vol->cluster_size_bits) {
+#define ESTR "Error reading attribute value"
+ if (r == -1)
+ ntfs_log_perror(ESTR);
+ else if (r < rl[i].length <<
+ vol->cluster_size_bits) {
+ ntfs_log_debug(ESTR ": Ran out of input data.\n");
+ errno = EIO;
+ } else {
+ ntfs_log_debug(ESTR ": unknown error\n");
+ errno = EIO;
+ }
+#undef ESTR
+ free(rl);
+ free(intbuf);
+ return 0;
+ }
+ memcpy(b + total, intbuf, sle64_to_cpu(a->data_size) -
+ total);
+ free(intbuf);
+ total = sle64_to_cpu(a->data_size);
+ break;
+ }
+ /*
+ * FIXME: If compressed file: Only read if lcn != -1.
+ * Otherwise, we are dealing with a sparse run and we just
+ * memset the user buffer to 0 for the length of the run, which
+ * should be 16 (= compression unit size).
+ * FIXME: Really only when file is compressed, or can
+ * we have sparse runs in uncompressed files as well?
+ * - Yes we can, in sparse files! But not necessarily size of
+ * 16, just run length.
+ */
+ r = ntfs_pread(vol->dev, rl[i].lcn << vol->cluster_size_bits,
+ rl[i].length << vol->cluster_size_bits,
+ b + total);
+ if (r != rl[i].length << vol->cluster_size_bits) {
+#define ESTR "Error reading attribute value"
+ if (r == -1)
+ ntfs_log_perror(ESTR);
+ else if (r < rl[i].length << vol->cluster_size_bits) {
+ ntfs_log_debug(ESTR ": Ran out of input data.\n");
+ errno = EIO;
+ } else {
+ ntfs_log_debug(ESTR ": unknown error\n");
+ errno = EIO;
+ }
+#undef ESTR
+ free(rl);
+ return 0;
+ }
+ total += r;
+ }
+ free(rl);
+ return total;
+}
+
+/* Already cleaned up code below, but still look for FIXME:... */
+
+/**
+ * __ntfs_attr_init - primary initialization of an ntfs attribute structure
+ * @na: ntfs attribute to initialize
+ * @ni: ntfs inode with which to initialize the ntfs attribute
+ * @type: attribute type
+ * @name: attribute name in little endian Unicode or NULL
+ * @name_len: length of attribute @name in Unicode characters (if @name given)
+ *
+ * Initialize the ntfs attribute @na with @ni, @type, @name, and @name_len.
+ */
+static void __ntfs_attr_init(ntfs_attr *na, ntfs_inode *ni,
+ const ATTR_TYPES type, ntfschar *name, const u32 name_len)
+{
+ na->rl = NULL;
+ na->ni = ni;
+ na->type = type;
+ na->name = name;
+ if (name)
+ na->name_len = name_len;
+ else
+ na->name_len = 0;
+}
+
+/**
+ * ntfs_attr_init - initialize an ntfs_attr with data sizes and status
+ * @na:
+ * @non_resident:
+ * @compressed:
+ * @encrypted:
+ * @sparse:
+ * @allocated_size:
+ * @data_size:
+ * @initialized_size:
+ * @compressed_size:
+ * @compression_unit:
+ *
+ * Final initialization for an ntfs attribute.
+ */
+void ntfs_attr_init(ntfs_attr *na, const BOOL non_resident,
+ const ATTR_FLAGS data_flags,
+ const BOOL encrypted, const BOOL sparse,
+ const s64 allocated_size, const s64 data_size,
+ const s64 initialized_size, const s64 compressed_size,
+ const u8 compression_unit)
+{
+ if (!NAttrInitialized(na)) {
+ na->data_flags = data_flags;
+ if (non_resident)
+ NAttrSetNonResident(na);
+ if (data_flags & ATTR_COMPRESSION_MASK)
+ NAttrSetCompressed(na);
+ if (encrypted)
+ NAttrSetEncrypted(na);
+ if (sparse)
+ NAttrSetSparse(na);
+ na->allocated_size = allocated_size;
+ na->data_size = data_size;
+ na->initialized_size = initialized_size;
+ if ((data_flags & ATTR_COMPRESSION_MASK) || sparse) {
+ ntfs_volume *vol = na->ni->vol;
+
+ na->compressed_size = compressed_size;
+ na->compression_block_clusters = 1 << compression_unit;
+ na->compression_block_size = 1 << (compression_unit +
+ vol->cluster_size_bits);
+ na->compression_block_size_bits = ffs(
+ na->compression_block_size) - 1;
+ }
+ NAttrSetInitialized(na);
+ }
+}
+
+/**
+ * ntfs_attr_open - open an ntfs attribute for access
+ * @ni: open ntfs inode in which the ntfs attribute resides
+ * @type: attribute type
+ * @name: attribute name in little endian Unicode or AT_UNNAMED or NULL
+ * @name_len: length of attribute @name in Unicode characters (if @name given)
+ *
+ * Allocate a new ntfs attribute structure, initialize it with @ni, @type,
+ * @name, and @name_len, then return it. Return NULL on error with
+ * errno set to the error code.
+ *
+ * If @name is AT_UNNAMED look specifically for an unnamed attribute. If you
+ * do not care whether the attribute is named or not set @name to NULL. In
+ * both those cases @name_len is not used at all.
+ */
+ntfs_attr *ntfs_attr_open(ntfs_inode *ni, const ATTR_TYPES type,
+ ntfschar *name, u32 name_len)
+{
+ ntfs_attr_search_ctx *ctx;
+ ntfs_attr *na = NULL;
+ ntfschar *newname = NULL;
+ ATTR_RECORD *a;
+ BOOL cs;
+
+ ntfs_log_enter("Entering for inode %lld, attr 0x%x.\n",
+ (unsigned long long)ni->mft_no, type);
+
+ if (!ni || !ni->vol || !ni->mrec) {
+ errno = EINVAL;
+ goto out;
+ }
+ na = ntfs_calloc(sizeof(ntfs_attr));
+ if (!na)
+ goto out;
+ if (name && name != AT_UNNAMED && name != NTFS_INDEX_I30) {
+ name = ntfs_ucsndup(name, name_len);
+ if (!name)
+ goto err_out;
+ newname = name;
+ }
+
+ ctx = ntfs_attr_get_search_ctx(ni, NULL);
+ if (!ctx)
+ goto err_out;
+
+ if (ntfs_attr_lookup(type, name, name_len, 0, 0, NULL, 0, ctx))
+ goto put_err_out;
+
+ a = ctx->attr;
+
+ if (!name) {
+ if (a->name_length) {
+ name = ntfs_ucsndup((ntfschar*)((u8*)a + le16_to_cpu(
+ a->name_offset)), a->name_length);
+ if (!name)
+ goto put_err_out;
+ newname = name;
+ name_len = a->name_length;
+ } else {
+ name = AT_UNNAMED;
+ name_len = 0;
+ }
+ }
+
+ __ntfs_attr_init(na, ni, type, name, name_len);
+
+ /*
+ * Wipe the flags in case they are not zero for an attribute list
+ * attribute. Windows does not complain about invalid flags and chkdsk
+ * does not detect or fix them so we need to cope with it, too.
+ */
+ if (type == AT_ATTRIBUTE_LIST)
+ a->flags = 0;
+
+ if ((type == AT_DATA) && !a->initialized_size) {
+ /*
+ * Define/redefine the compression state if stream is
+ * empty, based on the compression mark on parent
+ * directory (for unnamed data streams) or on current
+ * inode (for named data streams). The compression mark
+ * may change any time, the compression state can only
+ * change when stream is wiped out.
+ */
+ a->flags &= ~ATTR_COMPRESSION_MASK;
+ if ((na->ni->flags & FILE_ATTR_COMPRESSED)
+ && (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE))
+ a->flags |= ATTR_IS_COMPRESSED;
+ }
+
+ cs = a->flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE);
+
+ if (na->type == AT_DATA && na->name == AT_UNNAMED &&
+ ((!(a->flags & ATTR_IS_SPARSE) != !NAttrSparse(na)) ||
+ (!(a->flags & ATTR_IS_ENCRYPTED) != !NAttrEncrypted(na)))) {
+ errno = EIO;
+ ntfs_log_perror("Inode %lld has corrupt attribute flags "
+ "(0x%x <> 0x%x)",(unsigned long long)ni->mft_no,
+ a->flags, na->ni->flags);
+ goto put_err_out;
+ }
+
+ if (a->non_resident) {
+ if ((a->flags & ATTR_COMPRESSION_MASK)
+ && !a->compression_unit) {
+ errno = EIO;
+ ntfs_log_perror("Compressed inode %lld attr 0x%x has "
+ "no compression unit",
+ (unsigned long long)ni->mft_no, type);
+ goto put_err_out;
+ }
+ ntfs_attr_init(na, TRUE, a->flags,
+ a->flags & ATTR_IS_ENCRYPTED,
+ a->flags & ATTR_IS_SPARSE,
+ sle64_to_cpu(a->allocated_size),
+ sle64_to_cpu(a->data_size),
+ sle64_to_cpu(a->initialized_size),
+ cs ? sle64_to_cpu(a->compressed_size) : 0,
+ cs ? a->compression_unit : 0);
+ } else {
+ s64 l = le32_to_cpu(a->value_length);
+ ntfs_attr_init(na, FALSE, a->flags,
+ a->flags & ATTR_IS_ENCRYPTED,
+ a->flags & ATTR_IS_SPARSE, (l + 7) & ~7, l, l,
+ cs ? (l + 7) & ~7 : 0, 0);
+ }
+ ntfs_attr_put_search_ctx(ctx);
+out:
+ ntfs_log_leave("\n");
+ return na;
+
+put_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+err_out:
+ free(newname);
+ free(na);
+ na = NULL;
+ goto out;
+}
+
+/**
+ * ntfs_attr_close - free an ntfs attribute structure
+ * @na: ntfs attribute structure to free
+ *
+ * Release all memory associated with the ntfs attribute @na and then release
+ * @na itself.
+ */
+void ntfs_attr_close(ntfs_attr *na)
+{
+ if (!na)
+ return;
+ if (NAttrNonResident(na) && na->rl)
+ free(na->rl);
+ /* Don't release if using an internal constant. */
+ if (na->name != AT_UNNAMED && na->name != NTFS_INDEX_I30
+ && na->name != STREAM_SDS)
+ free(na->name);
+ free(na);
+}
+
+/**
+ * ntfs_attr_map_runlist - map (a part of) a runlist of an ntfs attribute
+ * @na: ntfs attribute for which to map (part of) a runlist
+ * @vcn: map runlist part containing this vcn
+ *
+ * Map the part of a runlist containing the @vcn of the ntfs attribute @na.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code.
+ */
+int ntfs_attr_map_runlist(ntfs_attr *na, VCN vcn)
+{
+ LCN lcn;
+ ntfs_attr_search_ctx *ctx;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn 0x%llx.\n",
+ (unsigned long long)na->ni->mft_no, na->type, (long long)vcn);
+
+ lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
+ if (lcn >= 0 || lcn == LCN_HOLE || lcn == LCN_ENOENT)
+ return 0;
+
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ return -1;
+
+ /* Find the attribute in the mft record. */
+ if (!ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
+ vcn, NULL, 0, ctx)) {
+ runlist_element *rl;
+
+ /* Decode the runlist. */
+ rl = ntfs_mapping_pairs_decompress(na->ni->vol, ctx->attr,
+ na->rl);
+ if (rl) {
+ na->rl = rl;
+ ntfs_attr_put_search_ctx(ctx);
+ return 0;
+ }
+ }
+
+ ntfs_attr_put_search_ctx(ctx);
+ return -1;
+}
+
+/**
+ * ntfs_attr_map_whole_runlist - map the whole runlist of an ntfs attribute
+ * @na: ntfs attribute for which to map the runlist
+ *
+ * Map the whole runlist of the ntfs attribute @na. For an attribute made up
+ * of only one attribute extent this is the same as calling
+ * ntfs_attr_map_runlist(na, 0) but for an attribute with multiple extents this
+ * will map the runlist fragments from each of the extents thus giving access
+ * to the entirety of the disk allocation of an attribute.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code.
+ */
+int ntfs_attr_map_whole_runlist(ntfs_attr *na)
+{
+ VCN next_vcn, last_vcn, highest_vcn;
+ ntfs_attr_search_ctx *ctx;
+ ntfs_volume *vol = na->ni->vol;
+ ATTR_RECORD *a;
+ int ret = -1;
+
+ ntfs_log_enter("Entering for inode %llu, attr 0x%x.\n",
+ (unsigned long long)na->ni->mft_no, na->type);
+
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ goto out;
+
+ /* Map all attribute extents one by one. */
+ next_vcn = last_vcn = highest_vcn = 0;
+ a = NULL;
+ while (1) {
+ runlist_element *rl;
+
+ int not_mapped = 0;
+ if (ntfs_rl_vcn_to_lcn(na->rl, next_vcn) == LCN_RL_NOT_MAPPED)
+ not_mapped = 1;
+
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len,
+ CASE_SENSITIVE, next_vcn, NULL, 0, ctx))
+ break;
+
+ a = ctx->attr;
+
+ if (not_mapped) {
+ /* Decode the runlist. */
+ rl = ntfs_mapping_pairs_decompress(na->ni->vol,
+ a, na->rl);
+ if (!rl)
+ goto err_out;
+ na->rl = rl;
+ }
+
+ /* Are we in the first extent? */
+ if (!next_vcn) {
+ if (a->lowest_vcn) {
+ errno = EIO;
+ ntfs_log_perror("First extent of inode %llu "
+ "attribute has non-zero lowest_vcn",
+ (unsigned long long)na->ni->mft_no);
+ goto err_out;
+ }
+ /* Get the last vcn in the attribute. */
+ last_vcn = sle64_to_cpu(a->allocated_size) >>
+ vol->cluster_size_bits;
+ }
+
+ /* Get the lowest vcn for the next extent. */
+ highest_vcn = sle64_to_cpu(a->highest_vcn);
+ next_vcn = highest_vcn + 1;
+
+ /* Only one extent or error, which we catch below. */
+ if (next_vcn <= 0) {
+ errno = ENOENT;
+ break;
+ }
+
+ /* Avoid endless loops due to corruption. */
+ if (next_vcn < sle64_to_cpu(a->lowest_vcn)) {
+ errno = EIO;
+ ntfs_log_perror("Inode %llu has corrupt attribute list",
+ (unsigned long long)na->ni->mft_no);
+ goto err_out;
+ }
+ }
+ if (!a) {
+ ntfs_log_perror("Couldn't find attribute for runlist mapping");
+ goto err_out;
+ }
+ if (highest_vcn && highest_vcn != last_vcn - 1) {
+ errno = EIO;
+ ntfs_log_perror("Failed to load full runlist: inode: %llu "
+ "highest_vcn: 0x%llx last_vcn: 0x%llx",
+ (unsigned long long)na->ni->mft_no,
+ (long long)highest_vcn, (long long)last_vcn);
+ goto err_out;
+ }
+ if (errno == ENOENT)
+ ret = 0;
+err_out:
+ ntfs_attr_put_search_ctx(ctx);
+out:
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+/**
+ * ntfs_attr_vcn_to_lcn - convert a vcn into a lcn given an ntfs attribute
+ * @na: ntfs attribute whose runlist to use for conversion
+ * @vcn: vcn to convert
+ *
+ * Convert the virtual cluster number @vcn of an attribute into a logical
+ * cluster number (lcn) of a device using the runlist @na->rl to map vcns to
+ * their corresponding lcns.
+ *
+ * If the @vcn is not mapped yet, attempt to map the attribute extent
+ * containing the @vcn and retry the vcn to lcn conversion.
+ *
+ * Since lcns must be >= 0, we use negative return values with special meaning:
+ *
+ * Return value Meaning / Description
+ * ==========================================
+ * -1 = LCN_HOLE Hole / not allocated on disk.
+ * -3 = LCN_ENOENT There is no such vcn in the attribute.
+ * -4 = LCN_EINVAL Input parameter error.
+ * -5 = LCN_EIO Corrupt fs, disk i/o error, or not enough memory.
+ */
+LCN ntfs_attr_vcn_to_lcn(ntfs_attr *na, const VCN vcn)
+{
+ LCN lcn;
+ BOOL is_retry = FALSE;
+
+ if (!na || !NAttrNonResident(na) || vcn < 0)
+ return (LCN)LCN_EINVAL;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
+ long)na->ni->mft_no, na->type);
+retry:
+ /* Convert vcn to lcn. If that fails map the runlist and retry once. */
+ lcn = ntfs_rl_vcn_to_lcn(na->rl, vcn);
+ if (lcn >= 0)
+ return lcn;
+ if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) {
+ is_retry = TRUE;
+ goto retry;
+ }
+ /*
+ * If the attempt to map the runlist failed, or we are getting
+ * LCN_RL_NOT_MAPPED despite having mapped the attribute extent
+ * successfully, something is really badly wrong...
+ */
+ if (!is_retry || lcn == (LCN)LCN_RL_NOT_MAPPED)
+ return (LCN)LCN_EIO;
+ /* lcn contains the appropriate error code. */
+ return lcn;
+}
+
+/**
+ * ntfs_attr_find_vcn - find a vcn in the runlist of an ntfs attribute
+ * @na: ntfs attribute whose runlist to search
+ * @vcn: vcn to find
+ *
+ * Find the virtual cluster number @vcn in the runlist of the ntfs attribute
+ * @na and return the the address of the runlist element containing the @vcn.
+ *
+ * Note you need to distinguish between the lcn of the returned runlist
+ * element being >= 0 and LCN_HOLE. In the later case you have to return zeroes
+ * on read and allocate clusters on write. You need to update the runlist, the
+ * attribute itself as well as write the modified mft record to disk.
+ *
+ * If there is an error return NULL with errno set to the error code. The
+ * following error codes are defined:
+ * EINVAL Input parameter error.
+ * ENOENT There is no such vcn in the runlist.
+ * ENOMEM Not enough memory.
+ * EIO I/O error or corrupt metadata.
+ */
+runlist_element *ntfs_attr_find_vcn(ntfs_attr *na, const VCN vcn)
+{
+ runlist_element *rl;
+ BOOL is_retry = FALSE;
+
+ if (!na || !NAttrNonResident(na) || vcn < 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, vcn %llx\n",
+ (unsigned long long)na->ni->mft_no, na->type,
+ (long long)vcn);
+retry:
+ rl = na->rl;
+ if (!rl)
+ goto map_rl;
+ if (vcn < rl[0].vcn)
+ goto map_rl;
+ while (rl->length) {
+ if (vcn < rl[1].vcn) {
+ if (rl->lcn >= (LCN)LCN_HOLE)
+ return rl;
+ break;
+ }
+ rl++;
+ }
+ switch (rl->lcn) {
+ case (LCN)LCN_RL_NOT_MAPPED:
+ goto map_rl;
+ case (LCN)LCN_ENOENT:
+ errno = ENOENT;
+ break;
+ case (LCN)LCN_EINVAL:
+ errno = EINVAL;
+ break;
+ default:
+ errno = EIO;
+ break;
+ }
+ return NULL;
+map_rl:
+ /* The @vcn is in an unmapped region, map the runlist and retry. */
+ if (!is_retry && !ntfs_attr_map_runlist(na, vcn)) {
+ is_retry = TRUE;
+ goto retry;
+ }
+ /*
+ * If we already retried or the mapping attempt failed something has
+ * gone badly wrong. EINVAL and ENOENT coming from a failed mapping
+ * attempt are equivalent to errors for us as they should not happen
+ * in our code paths.
+ */
+ if (is_retry || errno == EINVAL || errno == ENOENT)
+ errno = EIO;
+ return NULL;
+}
+
+/**
+ * ntfs_attr_pread_i - see description at ntfs_attr_pread()
+ */
+static s64 ntfs_attr_pread_i(ntfs_attr *na, const s64 pos, s64 count, void *b)
+{
+ s64 br, to_read, ofs, total, total2, max_read, max_init;
+ ntfs_volume *vol;
+ runlist_element *rl;
+ u16 efs_padding_length;
+
+ /* Sanity checking arguments is done in ntfs_attr_pread(). */
+
+ if ((na->data_flags & ATTR_COMPRESSION_MASK) && NAttrNonResident(na)) {
+ if ((na->data_flags & ATTR_COMPRESSION_MASK)
+ == ATTR_IS_COMPRESSED)
+ return ntfs_compressed_attr_pread(na, pos, count, b);
+ else {
+ /* compression mode not supported */
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+ }
+ /*
+ * Encrypted non-resident attributes are not supported. We return
+ * access denied, which is what Windows NT4 does, too.
+ * However, allow if mounted with efs_raw option
+ */
+ vol = na->ni->vol;
+ if (!vol->efs_raw && NAttrEncrypted(na) && NAttrNonResident(na)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ if (!count)
+ return 0;
+ /*
+ * Truncate reads beyond end of attribute,
+ * but round to next 512 byte boundary for encrypted
+ * attributes with efs_raw mount option
+ */
+ max_read = na->data_size;
+ max_init = na->initialized_size;
+ if (na->ni->vol->efs_raw
+ && (na->data_flags & ATTR_IS_ENCRYPTED)
+ && NAttrNonResident(na)) {
+ if (na->data_size != na->initialized_size) {
+ ntfs_log_error("uninitialized encrypted file not supported\n");
+ errno = EINVAL;
+ return -1;
+ }
+ max_init = max_read = ((na->data_size + 511) & ~511) + 2;
+ }
+ if (pos + count > max_read) {
+ if (pos >= max_read)
+ return 0;
+ count = max_read - pos;
+ }
+ /* If it is a resident attribute, get the value from the mft record. */
+ if (!NAttrNonResident(na)) {
+ ntfs_attr_search_ctx *ctx;
+ char *val;
+
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ return -1;
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
+ 0, NULL, 0, ctx)) {
+res_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+ return -1;
+ }
+ val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset);
+ if (val < (char*)ctx->attr || val +
+ le32_to_cpu(ctx->attr->value_length) >
+ (char*)ctx->mrec + vol->mft_record_size) {
+ errno = EIO;
+ ntfs_log_perror("%s: Sanity check failed", __FUNCTION__);
+ goto res_err_out;
+ }
+ memcpy(b, val + pos, count);
+ ntfs_attr_put_search_ctx(ctx);
+ return count;
+ }
+ total = total2 = 0;
+ /* Zero out reads beyond initialized size. */
+ if (pos + count > max_init) {
+ if (pos >= max_init) {
+ memset(b, 0, count);
+ return count;
+ }
+ total2 = pos + count - max_init;
+ count -= total2;
+ memset((u8*)b + count, 0, total2);
+ }
+ /*
+ * for encrypted non-resident attributes with efs_raw set
+ * the last two bytes aren't read from disk but contain
+ * the number of padding bytes so original size can be
+ * restored
+ */
+ if (na->ni->vol->efs_raw &&
+ (na->data_flags & ATTR_IS_ENCRYPTED) &&
+ ((pos + count) > max_init-2)) {
+ efs_padding_length = 511 - ((na->data_size - 1) & 511);
+ if (pos+count == max_init) {
+ if (count == 1) {
+ *((u8*)b+count-1) = (u8)(efs_padding_length >> 8);
+ count--;
+ total2++;
+ } else {
+ *(u16*)((u8*)b+count-2) = cpu_to_le16(efs_padding_length);
+ count -= 2;
+ total2 +=2;
+ }
+ } else {
+ *((u8*)b+count-1) = (u8)(efs_padding_length & 0xff);
+ count--;
+ total2++;
+ }
+ }
+
+ /* Find the runlist element containing the vcn. */
+ rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits);
+ if (!rl) {
+ /*
+ * If the vcn is not present it is an out of bounds read.
+ * However, we already truncated the read to the data_size,
+ * so getting this here is an error.
+ */
+ if (errno == ENOENT) {
+ errno = EIO;
+ ntfs_log_perror("%s: Failed to find VCN #1", __FUNCTION__);
+ }
+ return -1;
+ }
+ /*
+ * Gather the requested data into the linear destination buffer. Note,
+ * a partial final vcn is taken care of by the @count capping of read
+ * length.
+ */
+ ofs = pos - (rl->vcn << vol->cluster_size_bits);
+ for (; count; rl++, ofs = 0) {
+ if (rl->lcn == LCN_RL_NOT_MAPPED) {
+ rl = ntfs_attr_find_vcn(na, rl->vcn);
+ if (!rl) {
+ if (errno == ENOENT) {
+ errno = EIO;
+ ntfs_log_perror("%s: Failed to find VCN #2",
+ __FUNCTION__);
+ }
+ goto rl_err_out;
+ }
+ /* Needed for case when runs merged. */
+ ofs = pos + total - (rl->vcn << vol->cluster_size_bits);
+ }
+ if (!rl->length) {
+ errno = EIO;
+ ntfs_log_perror("%s: Zero run length", __FUNCTION__);
+ goto rl_err_out;
+ }
+ if (rl->lcn < (LCN)0) {
+ if (rl->lcn != (LCN)LCN_HOLE) {
+ ntfs_log_perror("%s: Bad run (%lld)",
+ __FUNCTION__,
+ (long long)rl->lcn);
+ goto rl_err_out;
+ }
+ /* It is a hole, just zero the matching @b range. */
+ to_read = min(count, (rl->length <<
+ vol->cluster_size_bits) - ofs);
+ memset(b, 0, to_read);
+ /* Update progress counters. */
+ total += to_read;
+ count -= to_read;
+ b = (u8*)b + to_read;
+ continue;
+ }
+ /* It is a real lcn, read it into @dst. */
+ to_read = min(count, (rl->length << vol->cluster_size_bits) -
+ ofs);
+retry:
+ ntfs_log_trace("Reading %lld bytes from vcn %lld, lcn %lld, ofs"
+ " %lld.\n", (long long)to_read, (long long)rl->vcn,
+ (long long )rl->lcn, (long long)ofs);
+ br = ntfs_pread(vol->dev, (rl->lcn << vol->cluster_size_bits) +
+ ofs, to_read, b);
+ /* If everything ok, update progress counters and continue. */
+ if (br > 0) {
+ total += br;
+ count -= br;
+ b = (u8*)b + br;
+ }
+ if (br == to_read)
+ continue;
+ /* If the syscall was interrupted, try again. */
+ if (br == (s64)-1 && errno == EINTR)
+ goto retry;
+ if (total)
+ return total;
+ if (!br)
+ errno = EIO;
+ ntfs_log_perror("%s: ntfs_pread failed", __FUNCTION__);
+ return -1;
+ }
+ /* Finally, return the number of bytes read. */
+ return total + total2;
+rl_err_out:
+ if (total)
+ return total;
+ errno = EIO;
+ return -1;
+}
+
+/**
+ * ntfs_attr_pread - read from an attribute specified by an ntfs_attr structure
+ * @na: ntfs attribute to read from
+ * @pos: byte position in the attribute to begin reading from
+ * @count: number of bytes to read
+ * @b: output data buffer
+ *
+ * This function will read @count bytes starting at offset @pos from the ntfs
+ * attribute @na into the data buffer @b.
+ *
+ * On success, return the number of successfully read bytes. If this number is
+ * lower than @count this means that the read reached end of file or that an
+ * error was encountered during the read so that the read is partial. 0 means
+ * end of file or nothing was read (also return 0 when @count is 0).
+ *
+ * On error and nothing has been read, return -1 with errno set appropriately
+ * to the return code of ntfs_pread(), or to EINVAL in case of invalid
+ * arguments.
+ */
+s64 ntfs_attr_pread(ntfs_attr *na, const s64 pos, s64 count, void *b)
+{
+ s64 ret;
+
+ if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: na=%p b=%p pos=%lld count=%lld",
+ __FUNCTION__, na, b, (long long)pos,
+ (long long)count);
+ return -1;
+ }
+
+ ntfs_log_enter("Entering for inode %lld attr 0x%x pos %lld count "
+ "%lld\n", (unsigned long long)na->ni->mft_no,
+ na->type, (long long)pos, (long long)count);
+
+ ret = ntfs_attr_pread_i(na, pos, count, b);
+
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+static int ntfs_attr_fill_zero(ntfs_attr *na, s64 pos, s64 count)
+{
+ char *buf;
+ s64 written, size, end = pos + count;
+ s64 ofsi;
+ const runlist_element *rli;
+ ntfs_volume *vol;
+ int ret = -1;
+
+ ntfs_log_trace("pos %lld, count %lld\n", (long long)pos,
+ (long long)count);
+
+ if (!na || pos < 0 || count < 0) {
+ errno = EINVAL;
+ goto err_out;
+ }
+
+ buf = ntfs_calloc(NTFS_BUF_SIZE);
+ if (!buf)
+ goto err_out;
+
+ rli = na->rl;
+ ofsi = 0;
+ vol = na->ni->vol;
+ while (pos < end) {
+ while (rli->length && (ofsi + (rli->length <<
+ vol->cluster_size_bits) <= pos)) {
+ ofsi += (rli->length << vol->cluster_size_bits);
+ rli++;
+ }
+ size = min(end - pos, NTFS_BUF_SIZE);
+ written = ntfs_rl_pwrite(vol, rli, ofsi, pos, size, buf);
+ if (written <= 0) {
+ ntfs_log_perror("Failed to zero space");
+ goto err_free;
+ }
+ pos += written;
+ }
+
+ ret = 0;
+err_free:
+ free(buf);
+err_out:
+ return ret;
+}
+
+static int ntfs_attr_fill_hole(ntfs_attr *na, s64 count, s64 *ofs,
+ runlist_element **rl, VCN *update_from)
+{
+ s64 to_write;
+ s64 need;
+ ntfs_volume *vol = na->ni->vol;
+ int eo, ret = -1;
+ runlist *rlc;
+ LCN lcn_seek_from = -1;
+ VCN cur_vcn, from_vcn;
+
+ to_write = min(count, ((*rl)->length << vol->cluster_size_bits) - *ofs);
+
+ cur_vcn = (*rl)->vcn;
+ from_vcn = (*rl)->vcn + (*ofs >> vol->cluster_size_bits);
+
+ ntfs_log_trace("count: %lld, cur_vcn: %lld, from: %lld, to: %lld, ofs: "
+ "%lld\n", (long long)count, (long long)cur_vcn,
+ (long long)from_vcn, (long long)to_write, (long long)*ofs);
+
+ /* Map whole runlist to be able update mapping pairs later. */
+ if (ntfs_attr_map_whole_runlist(na))
+ goto err_out;
+
+ /* Restore @*rl, it probably get lost during runlist mapping. */
+ *rl = ntfs_attr_find_vcn(na, cur_vcn);
+ if (!*rl) {
+ ntfs_log_error("Failed to find run after mapping runlist. "
+ "Please report to %s.\n", NTFS_DEV_LIST);
+ errno = EIO;
+ goto err_out;
+ }
+
+ /* Search backwards to find the best lcn to start seek from. */
+ rlc = *rl;
+ while (rlc->vcn) {
+ rlc--;
+ if (rlc->lcn >= 0) {
+ /*
+ * avoid fragmenting a compressed file
+ * Windows does not do that, and that may
+ * not be desirable for files which can
+ * be updated
+ */
+ if (na->data_flags & ATTR_COMPRESSION_MASK)
+ lcn_seek_from = rlc->lcn + rlc->length;
+ else
+ lcn_seek_from = rlc->lcn + (from_vcn - rlc->vcn);
+ break;
+ }
+ }
+ if (lcn_seek_from == -1) {
+ /* Backwards search failed, search forwards. */
+ rlc = *rl;
+ while (rlc->length) {
+ rlc++;
+ if (rlc->lcn >= 0) {
+ lcn_seek_from = rlc->lcn - (rlc->vcn - from_vcn);
+ if (lcn_seek_from < -1)
+ lcn_seek_from = -1;
+ break;
+ }
+ }
+ }
+
+ need = ((*ofs + to_write - 1) >> vol->cluster_size_bits)
+ + 1 + (*rl)->vcn - from_vcn;
+ if ((na->data_flags & ATTR_COMPRESSION_MASK)
+ && (need < na->compression_block_clusters)) {
+ /*
+ * for a compressed file, be sure to allocate the full hole.
+ * We may need space to decompress existing compressed data.
+ */
+ rlc = ntfs_cluster_alloc(vol, (*rl)->vcn, (*rl)->length,
+ lcn_seek_from, DATA_ZONE);
+ } else
+ rlc = ntfs_cluster_alloc(vol, from_vcn, need,
+ lcn_seek_from, DATA_ZONE);
+ if (!rlc)
+ goto err_out;
+
+ *rl = ntfs_runlists_merge(na->rl, rlc);
+ /*
+ * For a compressed attribute, we must be sure there is an
+ * available entry, so reserve it before it gets too late.
+ */
+ if (*rl && (na->data_flags & ATTR_COMPRESSION_MASK))
+ *rl = ntfs_rl_extend(*rl,1);
+ if (!*rl) {
+ eo = errno;
+ ntfs_log_perror("Failed to merge runlists");
+ if (ntfs_cluster_free_from_rl(vol, rlc)) {
+ ntfs_log_perror("Failed to free hot clusters. "
+ "Please run chkdsk /f");
+ }
+ errno = eo;
+ goto err_out;
+ }
+ na->rl = *rl;
+ if (*update_from == -1)
+ *update_from = from_vcn;
+ *rl = ntfs_attr_find_vcn(na, cur_vcn);
+ if (!*rl) {
+ /*
+ * It's definitely a BUG, if we failed to find @cur_vcn, because
+ * we missed it during instantiating of the hole.
+ */
+ ntfs_log_error("Failed to find run after hole instantiation. "
+ "Please report to %s.\n", NTFS_DEV_LIST);
+ errno = EIO;
+ goto err_out;
+ }
+ /* If leaved part of the hole go to the next run. */
+ if ((*rl)->lcn < 0)
+ (*rl)++;
+ /* Now LCN shoudn't be less than 0. */
+ if ((*rl)->lcn < 0) {
+ ntfs_log_error("BUG! LCN is lesser than 0. "
+ "Please report to the %s.\n", NTFS_DEV_LIST);
+ errno = EIO;
+ goto err_out;
+ }
+ if (*ofs) {
+ /* Clear non-sparse region from @cur_vcn to @*ofs. */
+ if (ntfs_attr_fill_zero(na, cur_vcn << vol->cluster_size_bits,
+ *ofs))
+ goto err_out;
+ }
+ if ((*rl)->vcn < cur_vcn) {
+ /*
+ * Clusters that replaced hole are merged with
+ * previous run, so we need to update offset.
+ */
+ *ofs += (cur_vcn - (*rl)->vcn) << vol->cluster_size_bits;
+ }
+ if ((*rl)->vcn > cur_vcn) {
+ /*
+ * We left part of the hole, so we need to update offset
+ */
+ *ofs -= ((*rl)->vcn - cur_vcn) << vol->cluster_size_bits;
+ }
+
+ ret = 0;
+err_out:
+ return ret;
+}
+
+static int stuff_hole(ntfs_attr *na, const s64 pos);
+
+/**
+ * ntfs_attr_pwrite - positioned write to an ntfs attribute
+ * @na: ntfs attribute to write to
+ * @pos: position in the attribute to write to
+ * @count: number of bytes to write
+ * @b: data buffer to write to disk
+ *
+ * This function will write @count bytes from data buffer @b to ntfs attribute
+ * @na at position @pos.
+ *
+ * On success, return the number of successfully written bytes. If this number
+ * is lower than @count this means that an error was encountered during the
+ * write so that the write is partial. 0 means nothing was written (also return
+ * 0 when @count is 0).
+ *
+ * On error and nothing has been written, return -1 with errno set
+ * appropriately to the return code of ntfs_pwrite(), or to EINVAL in case of
+ * invalid arguments.
+ */
+s64 ntfs_attr_pwrite(ntfs_attr *na, const s64 pos, s64 count, const void *b)
+{
+ s64 written, to_write, ofs, old_initialized_size, old_data_size;
+ s64 total = 0;
+ VCN update_from = -1;
+ ntfs_volume *vol;
+ s64 fullcount;
+ ntfs_attr_search_ctx *ctx = NULL;
+ runlist_element *rl;
+ s64 hole_end;
+ int eo;
+ int compressed_part;
+ struct {
+ unsigned int undo_initialized_size : 1;
+ unsigned int undo_data_size : 1;
+ } need_to = { 0, 0 };
+ BOOL makingnonresident = FALSE;
+ BOOL wasnonresident = FALSE;
+ BOOL compressed;
+
+ ntfs_log_enter("Entering for inode %lld, attr 0x%x, pos 0x%llx, count "
+ "0x%llx.\n", (long long)na->ni->mft_no, na->type,
+ (long long)pos, (long long)count);
+
+ if (!na || !na->ni || !na->ni->vol || !b || pos < 0 || count < 0) {
+ errno = EINVAL;
+ ntfs_log_perror("%s", __FUNCTION__);
+ goto errno_set;
+ }
+ vol = na->ni->vol;
+ compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
+ != const_cpu_to_le16(0);
+ /*
+ * Encrypted attributes are only supported in raw mode. We return
+ * access denied, which is what Windows NT4 does, too.
+ * Moreover a file cannot be both encrypted and compressed.
+ */
+ if ((na->data_flags & ATTR_IS_ENCRYPTED)
+ && (compressed || !vol->efs_raw)) {
+ errno = EACCES;
+ goto errno_set;
+ }
+ /*
+ * Fill the gap, when writing beyond the end of a compressed
+ * file. This will make recursive calls
+ */
+ if (compressed
+ && (na->type == AT_DATA)
+ && (pos > na->initialized_size)
+ && stuff_hole(na,pos))
+ goto errno_set;
+ /* If this is a compressed attribute it needs special treatment. */
+ wasnonresident = NAttrNonResident(na) != 0;
+ makingnonresident = wasnonresident /* yes : already changed */
+ && !pos && (count == na->initialized_size);
+ /*
+ * Writing to compressed files is currently restricted
+ * to appending data. However we have to accept
+ * recursive write calls to make the attribute non resident.
+ * These are writing at position 0 up to initialized_size.
+ * Compression is also restricted to data streams.
+ * Only ATTR_IS_COMPRESSED compression mode is supported.
+ */
+ if (compressed
+ && ((na->type != AT_DATA)
+ || ((na->data_flags & ATTR_COMPRESSION_MASK)
+ != ATTR_IS_COMPRESSED)
+ || ((pos != na->initialized_size)
+ && (pos || (count != na->initialized_size))))) {
+ // TODO: Implement writing compressed attributes! (AIA)
+ errno = EOPNOTSUPP;
+ goto errno_set;
+ }
+
+ if (!count)
+ goto out;
+ /* for a compressed file, get prepared to reserve a full block */
+ fullcount = count;
+ /* If the write reaches beyond the end, extend the attribute. */
+ old_data_size = na->data_size;
+ if (pos + count > na->data_size) {
+ if (ntfs_attr_truncate(na, pos + count)) {
+ ntfs_log_perror("Failed to enlarge attribute");
+ goto errno_set;
+ }
+ /* resizing may change the compression mode */
+ compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
+ != const_cpu_to_le16(0);
+ need_to.undo_data_size = 1;
+ }
+ /*
+ * For compressed data, a single full block was allocated
+ * to deal with compression, possibly in a previous call.
+ * We are not able to process several blocks because
+ * some clusters are freed after compression and
+ * new allocations have to be done before proceeding,
+ * so truncate the requested count if needed (big buffers).
+ */
+ if (compressed) {
+ fullcount = na->data_size - pos;
+ if (count > fullcount)
+ count = fullcount;
+ }
+ old_initialized_size = na->initialized_size;
+ /* If it is a resident attribute, write the data to the mft record. */
+ if (!NAttrNonResident(na)) {
+ char *val;
+
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ goto err_out;
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
+ 0, NULL, 0, ctx)) {
+ ntfs_log_perror("%s: lookup failed", __FUNCTION__);
+ goto err_out;
+ }
+ val = (char*)ctx->attr + le16_to_cpu(ctx->attr->value_offset);
+ if (val < (char*)ctx->attr || val +
+ le32_to_cpu(ctx->attr->value_length) >
+ (char*)ctx->mrec + vol->mft_record_size) {
+ errno = EIO;
+ ntfs_log_perror("%s: Sanity check failed", __FUNCTION__);
+ goto err_out;
+ }
+ memcpy(val + pos, b, count);
+ if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no,
+ ctx->mrec)) {
+ /*
+ * NOTE: We are in a bad state at this moment. We have
+ * dirtied the mft record but we failed to commit it to
+ * disk. Since we have read the mft record ok before,
+ * it is unlikely to fail writing it, so is ok to just
+ * return error here... (AIA)
+ */
+ ntfs_log_perror("%s: failed to write mft record", __FUNCTION__);
+ goto err_out;
+ }
+ ntfs_attr_put_search_ctx(ctx);
+ total = count;
+ goto out;
+ }
+
+ /* Handle writes beyond initialized_size. */
+ if (pos + count > na->initialized_size) {
+ if (ntfs_attr_map_whole_runlist(na))
+ goto err_out;
+ /*
+ * For a compressed attribute, we must be sure there is an
+ * available entry, and, when reopening a compressed file,
+ * we may need to split a hole. So reserve the entries
+ * before it gets too late.
+ */
+ if (compressed) {
+ na->rl = ntfs_rl_extend(na->rl,2);
+ if (!na->rl)
+ goto err_out;
+ }
+ /* Set initialized_size to @pos + @count. */
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ goto err_out;
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0,
+ 0, NULL, 0, ctx))
+ goto err_out;
+
+ /* If write starts beyond initialized_size, zero the gap. */
+ if (pos > na->initialized_size)
+ if (ntfs_attr_fill_zero(na, na->initialized_size,
+ pos - na->initialized_size))
+ goto err_out;
+
+ ctx->attr->initialized_size = cpu_to_sle64(pos + count);
+ /* fix data_size for compressed files */
+ if (compressed)
+ ctx->attr->data_size = ctx->attr->initialized_size;
+ if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no,
+ ctx->mrec)) {
+ /*
+ * Undo the change in the in-memory copy and send it
+ * back for writing.
+ */
+ ctx->attr->initialized_size =
+ cpu_to_sle64(old_initialized_size);
+ ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no,
+ ctx->mrec);
+ goto err_out;
+ }
+ na->initialized_size = pos + count;
+#if CACHE_NIDATA_SIZE
+ if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
+ ? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30
+ : na->type == AT_DATA && na->name == AT_UNNAMED) {
+ na->ni->data_size = na->data_size;
+ na->ni->allocated_size = na->allocated_size;
+ set_nino_flag(na->ni,KnownSize);
+ }
+#endif
+ ntfs_attr_put_search_ctx(ctx);
+ ctx = NULL;
+ /*
+ * NOTE: At this point the initialized_size in the mft record
+ * has been updated BUT there is random data on disk thus if
+ * we decide to abort, we MUST change the initialized_size
+ * again.
+ */
+ need_to.undo_initialized_size = 1;
+ }
+ /* Find the runlist element containing the vcn. */
+ rl = ntfs_attr_find_vcn(na, pos >> vol->cluster_size_bits);
+ if (!rl) {
+ /*
+ * If the vcn is not present it is an out of bounds write.
+ * However, we already extended the size of the attribute,
+ * so getting this here must be an error of some kind.
+ */
+ if (errno == ENOENT) {
+ errno = EIO;
+ ntfs_log_perror("%s: Failed to find VCN #3", __FUNCTION__);
+ }
+ goto err_out;
+ }
+ ofs = pos - (rl->vcn << vol->cluster_size_bits);
+ /*
+ * Determine if there is compressed data in the current
+ * compression block (when appending to an existing file).
+ * If so, decompression will be needed, and the full block
+ * must be allocated to be identified as uncompressed.
+ * This comes in two variants, depending on whether
+ * compression has saved at least one cluster.
+ * The compressed size can never be over full size by
+ * more than 485 (maximum for 15 compression blocks
+ * compressed to 4098 and the last 3640 bytes compressed
+ * to 3640 + 3640/8 = 4095, with 15*2 + 4095 - 3640 = 485)
+ * This is less than the smallest cluster, so the hole is
+ * is never beyond the cluster next to the position of
+ * the first uncompressed byte to write.
+ */
+ compressed_part = 0;
+ if (compressed) {
+ if ((rl->lcn == (LCN)LCN_HOLE)
+ && wasnonresident) {
+ if (rl->length < na->compression_block_clusters)
+ compressed_part
+ = na->compression_block_clusters
+ - rl->length;
+ else {
+ compressed_part
+ = na->compression_block_clusters;
+ if (rl->length > na->compression_block_clusters) {
+ rl[2].lcn = rl[1].lcn;
+ rl[2].vcn = rl[1].vcn;
+ rl[2].length = rl[1].length;
+ rl[1].vcn -= compressed_part;
+ rl[1].lcn = LCN_HOLE;
+ rl[1].length = compressed_part;
+ rl[0].length -= compressed_part;
+ ofs -= rl->length << vol->cluster_size_bits;
+ rl++;
+ }
+ }
+ /* normal hole filling will do later */
+ } else
+ if ((rl->lcn >= 0) && (rl[1].lcn == (LCN)LCN_HOLE)) {
+ s64 xofs;
+
+ if (wasnonresident)
+ compressed_part = na->compression_block_clusters
+ - rl[1].length;
+ rl++;
+ xofs = 0;
+ if (ntfs_attr_fill_hole(na,
+ rl->length << vol->cluster_size_bits,
+ &xofs, &rl, &update_from))
+ goto err_out;
+ /* the fist allocated cluster was not merged */
+ if (!xofs)
+ rl--;
+ }
+ }
+ /*
+ * Scatter the data from the linear data buffer to the volume. Note, a
+ * partial final vcn is taken care of by the @count capping of write
+ * length.
+ */
+ for (hole_end = 0; count; rl++, ofs = 0, hole_end = 0) {
+ if (rl->lcn == LCN_RL_NOT_MAPPED) {
+ rl = ntfs_attr_find_vcn(na, rl->vcn);
+ if (!rl) {
+ if (errno == ENOENT) {
+ errno = EIO;
+ ntfs_log_perror("%s: Failed to find VCN"
+ " #4", __FUNCTION__);
+ }
+ goto rl_err_out;
+ }
+ /* Needed for case when runs merged. */
+ ofs = pos + total - (rl->vcn << vol->cluster_size_bits);
+ }
+ if (!rl->length) {
+ errno = EIO;
+ ntfs_log_perror("%s: Zero run length", __FUNCTION__);
+ goto rl_err_out;
+ }
+ if (rl->lcn < (LCN)0) {
+ hole_end = rl->vcn + rl->length;
+
+ if (rl->lcn != (LCN)LCN_HOLE) {
+ errno = EIO;
+ ntfs_log_perror("%s: Unexpected LCN (%lld)",
+ __FUNCTION__,
+ (long long)rl->lcn);
+ goto rl_err_out;
+ }
+ if (ntfs_attr_fill_hole(na, fullcount, &ofs, &rl,
+ &update_from))
+ goto err_out;
+ }
+ if (compressed) {
+ while (rl->length
+ && (ofs >= (rl->length << vol->cluster_size_bits))) {
+ ofs -= rl->length << vol->cluster_size_bits;
+ rl++;
+ }
+ }
+
+ /* It is a real lcn, write it to the volume. */
+ to_write = min(count, (rl->length << vol->cluster_size_bits) - ofs);
+retry:
+ ntfs_log_trace("Writing %lld bytes to vcn %lld, lcn %lld, ofs "
+ "%lld.\n", (long long)to_write, (long long)rl->vcn,
+ (long long)rl->lcn, (long long)ofs);
+ if (!NVolReadOnly(vol)) {
+
+ s64 wpos = (rl->lcn << vol->cluster_size_bits) + ofs;
+ s64 wend = (rl->vcn << vol->cluster_size_bits) + ofs + to_write;
+ u32 bsize = vol->cluster_size;
+ /* Byte size needed to zero fill a cluster */
+ s64 rounding = ((wend + bsize - 1) & ~(s64)(bsize - 1)) - wend;
+ /**
+ * Zero fill to cluster boundary if we're writing at the
+ * end of the attribute or into an ex-sparse cluster.
+ * This will cause the kernel not to seek and read disk
+ * blocks during write(2) to fill the end of the buffer
+ * which increases write speed by 2-10 fold typically.
+ *
+ * This is done even for compressed files, because
+ * data is generally first written uncompressed.
+ */
+ if (rounding && ((wend == na->initialized_size) ||
+ (wend < (hole_end << vol->cluster_size_bits)))){
+
+ char *cb;
+
+ rounding += to_write;
+
+ cb = ntfs_malloc(rounding);
+ if (!cb)
+ goto err_out;
+
+ memcpy(cb, b, to_write);
+ memset(cb + to_write, 0, rounding - to_write);
+
+ if (compressed) {
+ written = ntfs_compressed_pwrite(na,
+ rl, wpos, ofs, to_write,
+ rounding, b, compressed_part);
+ } else {
+ written = ntfs_pwrite(vol->dev, wpos,
+ rounding, cb);
+ if (written == rounding)
+ written = to_write;
+ }
+
+ free(cb);
+ } else {
+ if (compressed) {
+ written = ntfs_compressed_pwrite(na,
+ rl, wpos, ofs, to_write,
+ to_write, b, compressed_part);
+ } else
+ written = ntfs_pwrite(vol->dev, wpos,
+ to_write, b);
+ }
+ } else
+ written = to_write;
+ /* If everything ok, update progress counters and continue. */
+ if (written > 0) {
+ total += written;
+ count -= written;
+ fullcount -= written;
+ b = (const u8*)b + written;
+ }
+ if (written != to_write) {
+ /* Partial write cannot be dealt with, stop there */
+ /* If the syscall was interrupted, try again. */
+ if (written == (s64)-1 && errno == EINTR)
+ goto retry;
+ if (!written)
+ errno = EIO;
+ goto rl_err_out;
+ }
+ compressed_part = 0;
+ }
+done:
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+ /* Update mapping pairs if needed. */
+ if ((update_from != -1)
+ || (compressed && !makingnonresident))
+ if (ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/)) {
+ /*
+ * FIXME: trying to recover by goto rl_err_out;
+ * could cause driver hang by infinite looping.
+ */
+ total = -1;
+ goto out;
+ }
+out:
+ ntfs_log_leave("\n");
+ return total;
+rl_err_out:
+ eo = errno;
+ if (total) {
+ if (need_to.undo_initialized_size) {
+ if (pos + total > na->initialized_size)
+ goto done;
+ /*
+ * TODO: Need to try to change initialized_size. If it
+ * succeeds goto done, otherwise goto err_out. (AIA)
+ */
+ goto err_out;
+ }
+ goto done;
+ }
+ errno = eo;
+err_out:
+ eo = errno;
+ if (need_to.undo_initialized_size) {
+ int err;
+
+ err = 0;
+ if (!ctx) {
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ err = 1;
+ } else
+ ntfs_attr_reinit_search_ctx(ctx);
+ if (!err) {
+ err = ntfs_attr_lookup(na->type, na->name,
+ na->name_len, 0, 0, NULL, 0, ctx);
+ if (!err) {
+ na->initialized_size = old_initialized_size;
+ ctx->attr->initialized_size = cpu_to_sle64(
+ old_initialized_size);
+ err = ntfs_mft_record_write(vol,
+ ctx->ntfs_ino->mft_no,
+ ctx->mrec);
+ }
+ }
+ if (err) {
+ /*
+ * FIXME: At this stage could try to recover by filling
+ * old_initialized_size -> new_initialized_size with
+ * data or at least zeroes. (AIA)
+ */
+ ntfs_log_error("Eeek! Failed to recover from error. "
+ "Leaving metadata in inconsistent "
+ "state! Run chkdsk!\n");
+ }
+ }
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+ /* Update mapping pairs if needed. */
+ if (update_from != -1)
+ ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/);
+ /* Restore original data_size if needed. */
+ if (need_to.undo_data_size && ntfs_attr_truncate(na, old_data_size))
+ ntfs_log_perror("Failed to restore data_size");
+ errno = eo;
+errno_set:
+ total = -1;
+ goto out;
+}
+
+int ntfs_attr_pclose(ntfs_attr *na)
+{
+ s64 ofs;
+ int failed;
+ BOOL ok = TRUE;
+ VCN update_from = -1;
+ ntfs_volume *vol;
+ ntfs_attr_search_ctx *ctx = NULL;
+ runlist_element *rl;
+ int eo;
+ s64 hole;
+ int compressed_part;
+ BOOL compressed;
+
+ ntfs_log_enter("Entering for inode 0x%llx, attr 0x%x.\n",
+ na->ni->mft_no, na->type);
+
+ if (!na || !na->ni || !na->ni->vol) {
+ errno = EINVAL;
+ ntfs_log_perror("%s", __FUNCTION__);
+ goto errno_set;
+ }
+ vol = na->ni->vol;
+ compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
+ != const_cpu_to_le16(0);
+ /*
+ * Encrypted non-resident attributes are not supported. We return
+ * access denied, which is what Windows NT4 does, too.
+ */
+ if (NAttrEncrypted(na) && NAttrNonResident(na)) {
+ errno = EACCES;
+ goto errno_set;
+ }
+ /* If this is not a compressed attribute get out */
+ /* same if it is resident */
+ if (!compressed || !NAttrNonResident(na))
+ goto out;
+
+ /*
+ * For a compressed attribute, we must be sure there is an
+ * available entry, so reserve it before it gets too late.
+ */
+ if (ntfs_attr_map_whole_runlist(na))
+ goto err_out;
+ na->rl = ntfs_rl_extend(na->rl,1);
+ if (!na->rl)
+ goto err_out;
+ /* Find the runlist element containing the terminal vcn. */
+ rl = ntfs_attr_find_vcn(na, (na->initialized_size - 1) >> vol->cluster_size_bits);
+ if (!rl) {
+ /*
+ * If the vcn is not present it is an out of bounds write.
+ * However, we have already written the last byte uncompressed,
+ * so getting this here must be an error of some kind.
+ */
+ if (errno == ENOENT) {
+ errno = EIO;
+ ntfs_log_perror("%s: Failed to find VCN #5", __FUNCTION__);
+ }
+ goto err_out;
+ }
+ /*
+ * Scatter the data from the linear data buffer to the volume. Note, a
+ * partial final vcn is taken care of by the @count capping of write
+ * length.
+ */
+ compressed_part = 0;
+ if ((rl->lcn >= 0) && (rl[1].lcn == (LCN)LCN_HOLE))
+ compressed_part
+ = na->compression_block_clusters - rl[1].length;
+ else
+ if (rl->lcn == (LCN)LCN_HOLE) {
+ if (rl->length < na->compression_block_clusters)
+ compressed_part
+ = na->compression_block_clusters
+ - rl->length;
+ else
+ compressed_part
+ = na->compression_block_clusters;
+ }
+ /* done, if the last block set was compressed */
+ if (compressed_part)
+ goto out;
+
+ ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits);
+
+ if (rl->lcn == LCN_RL_NOT_MAPPED) {
+ rl = ntfs_attr_find_vcn(na, rl->vcn);
+ if (!rl) {
+ if (errno == ENOENT) {
+ errno = EIO;
+ ntfs_log_perror("%s: Failed to find VCN"
+ " #6", __FUNCTION__);
+ }
+ goto rl_err_out;
+ }
+ /* Needed for case when runs merged. */
+ ofs = na->initialized_size - (rl->vcn << vol->cluster_size_bits);
+ }
+ if (!rl->length) {
+ errno = EIO;
+ ntfs_log_perror("%s: Zero run length", __FUNCTION__);
+ goto rl_err_out;
+ }
+ if (rl->lcn < (LCN)0) {
+ hole = rl->vcn + rl->length;
+ if (rl->lcn != (LCN)LCN_HOLE) {
+ errno = EIO;
+ ntfs_log_perror("%s: Unexpected LCN (%lld)",
+ __FUNCTION__,
+ (long long)rl->lcn);
+ goto rl_err_out;
+ }
+
+ if (ntfs_attr_fill_hole(na, (s64)0, &ofs, &rl, &update_from))
+ goto err_out;
+ }
+ while (rl->length
+ && (ofs >= (rl->length << vol->cluster_size_bits))) {
+ ofs -= rl->length << vol->cluster_size_bits;
+ rl++;
+ }
+
+retry:
+ failed = 0;
+ if (!NVolReadOnly(vol)) {
+ failed = ntfs_compressed_close(na, rl, ofs);
+#if CACHE_NIDATA_SIZE
+ if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
+ ? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30
+ : na->type == AT_DATA && na->name == AT_UNNAMED) {
+ na->ni->data_size = na->data_size;
+ na->ni->allocated_size = na->allocated_size;
+ set_nino_flag(na->ni,KnownSize);
+ }
+#endif
+ }
+ if (failed) {
+ /* If the syscall was interrupted, try again. */
+ if (errno == EINTR)
+ goto retry;
+ else
+ goto rl_err_out;
+ }
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+ /* Update mapping pairs if needed. */
+ if (ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/)) {
+ /*
+ * FIXME: trying to recover by goto rl_err_out;
+ * could cause driver hang by infinite looping.
+ */
+ ok = FALSE;
+ goto out;
+ }
+out:
+ ntfs_log_leave("\n");
+ return (!ok);
+rl_err_out:
+ /*
+ * need not restore old sizes, only compressed_size
+ * can have changed. It has been set according to
+ * the current runlist while updating the mapping pairs,
+ * and must be kept consistent with the runlists.
+ */
+err_out:
+ eo = errno;
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+ /* Update mapping pairs if needed. */
+ ntfs_attr_update_mapping_pairs(na, 0 /*update_from*/);
+ errno = eo;
+errno_set:
+ ok = FALSE;
+ goto out;
+}
+
+/**
+ * ntfs_attr_mst_pread - multi sector transfer protected ntfs attribute read
+ * @na: multi sector transfer protected ntfs attribute to read from
+ * @pos: byte position in the attribute to begin reading from
+ * @bk_cnt: number of mst protected blocks to read
+ * @bk_size: size of each mst protected block in bytes
+ * @dst: output data buffer
+ *
+ * This function will read @bk_cnt blocks of size @bk_size bytes each starting
+ * at offset @pos from the ntfs attribute @na into the data buffer @b.
+ *
+ * On success, the multi sector transfer fixups are applied and the number of
+ * read blocks is returned. If this number is lower than @bk_cnt this means
+ * that the read has either reached end of attribute or that an error was
+ * encountered during the read so that the read is partial. 0 means end of
+ * attribute or nothing to read (also return 0 when @bk_cnt or @bk_size are 0).
+ *
+ * On error and nothing has been read, return -1 with errno set appropriately
+ * to the return code of ntfs_attr_pread() or to EINVAL in case of invalid
+ * arguments.
+ *
+ * NOTE: If an incomplete multi sector transfer is detected the magic is
+ * changed to BAAD but no error is returned, i.e. it is possible that any of
+ * the returned blocks have multi sector transfer errors. This should be
+ * detected by the caller by checking each block with is_baad_recordp(&block).
+ * The reasoning is that we want to fixup as many blocks as possible and we
+ * want to return even bad ones to the caller so, e.g. in case of ntfsck, the
+ * errors can be repaired.
+ */
+s64 ntfs_attr_mst_pread(ntfs_attr *na, const s64 pos, const s64 bk_cnt,
+ const u32 bk_size, void *dst)
+{
+ s64 br;
+ u8 *end;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n",
+ (unsigned long long)na->ni->mft_no, na->type,
+ (long long)pos);
+ if (bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE) {
+ errno = EINVAL;
+ ntfs_log_perror("%s", __FUNCTION__);
+ return -1;
+ }
+ br = ntfs_attr_pread(na, pos, bk_cnt * bk_size, dst);
+ if (br <= 0)
+ return br;
+ br /= bk_size;
+ for (end = (u8*)dst + br * bk_size; (u8*)dst < end; dst = (u8*)dst +
+ bk_size)
+ ntfs_mst_post_read_fixup((NTFS_RECORD*)dst, bk_size);
+ /* Finally, return the number of blocks read. */
+ return br;
+}
+
+/**
+ * ntfs_attr_mst_pwrite - multi sector transfer protected ntfs attribute write
+ * @na: multi sector transfer protected ntfs attribute to write to
+ * @pos: position in the attribute to write to
+ * @bk_cnt: number of mst protected blocks to write
+ * @bk_size: size of each mst protected block in bytes
+ * @src: data buffer to write to disk
+ *
+ * This function will write @bk_cnt blocks of size @bk_size bytes each from
+ * data buffer @b to multi sector transfer (mst) protected ntfs attribute @na
+ * at position @pos.
+ *
+ * On success, return the number of successfully written blocks. If this number
+ * is lower than @bk_cnt this means that an error was encountered during the
+ * write so that the write is partial. 0 means nothing was written (also
+ * return 0 when @bk_cnt or @bk_size are 0).
+ *
+ * On error and nothing has been written, return -1 with errno set
+ * appropriately to the return code of ntfs_attr_pwrite(), or to EINVAL in case
+ * of invalid arguments.
+ *
+ * NOTE: We mst protect the data, write it, then mst deprotect it using a quick
+ * deprotect algorithm (no checking). This saves us from making a copy before
+ * the write and at the same time causes the usn to be incremented in the
+ * buffer. This conceptually fits in better with the idea that cached data is
+ * always deprotected and protection is performed when the data is actually
+ * going to hit the disk and the cache is immediately deprotected again
+ * simulating an mst read on the written data. This way cache coherency is
+ * achieved.
+ */
+s64 ntfs_attr_mst_pwrite(ntfs_attr *na, const s64 pos, s64 bk_cnt,
+ const u32 bk_size, void *src)
+{
+ s64 written, i;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr type 0x%x, pos 0x%llx.\n",
+ (unsigned long long)na->ni->mft_no, na->type,
+ (long long)pos);
+ if (bk_cnt < 0 || bk_size % NTFS_BLOCK_SIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!bk_cnt)
+ return 0;
+ /* Prepare data for writing. */
+ for (i = 0; i < bk_cnt; ++i) {
+ int err;
+
+ err = ntfs_mst_pre_write_fixup((NTFS_RECORD*)
+ ((u8*)src + i * bk_size), bk_size);
+ if (err < 0) {
+ /* Abort write at this position. */
+ ntfs_log_perror("%s #1", __FUNCTION__);
+ if (!i)
+ return err;
+ bk_cnt = i;
+ break;
+ }
+ }
+ /* Write the prepared data. */
+ written = ntfs_attr_pwrite(na, pos, bk_cnt * bk_size, src);
+ if (written <= 0) {
+ ntfs_log_perror("%s: written=%lld", __FUNCTION__,
+ (long long)written);
+ }
+ /* Quickly deprotect the data again. */
+ for (i = 0; i < bk_cnt; ++i)
+ ntfs_mst_post_write_fixup((NTFS_RECORD*)((u8*)src + i *
+ bk_size));
+ if (written <= 0)
+ return written;
+ /* Finally, return the number of complete blocks written. */
+ return written / bk_size;
+}
+
+/**
+ * ntfs_attr_find - find (next) attribute in mft record
+ * @type: attribute type to find
+ * @name: attribute name to find (optional, i.e. NULL means don't care)
+ * @name_len: attribute name length (only needed if @name present)
+ * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present)
+ * @val: attribute value to find (optional, resident attributes only)
+ * @val_len: attribute value length
+ * @ctx: search context with mft record and attribute to search from
+ *
+ * You shouldn't need to call this function directly. Use lookup_attr() instead.
+ *
+ * ntfs_attr_find() takes a search context @ctx as parameter and searches the
+ * mft record specified by @ctx->mrec, beginning at @ctx->attr, for an
+ * attribute of @type, optionally @name and @val. If found, ntfs_attr_find()
+ * returns 0 and @ctx->attr will point to the found attribute.
+ *
+ * If not found, ntfs_attr_find() returns -1, with errno set to ENOENT and
+ * @ctx->attr will point to the attribute before which the attribute being
+ * searched for would need to be inserted if such an action were to be desired.
+ *
+ * On actual error, ntfs_attr_find() returns -1 with errno set to the error
+ * code but not to ENOENT. In this case @ctx->attr is undefined and in
+ * particular do not rely on it not changing.
+ *
+ * If @ctx->is_first is TRUE, the search begins with @ctx->attr itself. If it
+ * is FALSE, the search begins after @ctx->attr.
+ *
+ * If @type is AT_UNUSED, return the first found attribute, i.e. one can
+ * enumerate all attributes by setting @type to AT_UNUSED and then calling
+ * ntfs_attr_find() repeatedly until it returns -1 with errno set to ENOENT to
+ * indicate that there are no more entries. During the enumeration, each
+ * successful call of ntfs_attr_find() will return the next attribute in the
+ * mft record @ctx->mrec.
+ *
+ * If @type is AT_END, seek to the end and return -1 with errno set to ENOENT.
+ * AT_END is not a valid attribute, its length is zero for example, thus it is
+ * safer to return error instead of success in this case. This also allows us
+ * to interoperate cleanly with ntfs_external_attr_find().
+ *
+ * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present
+ * but not AT_UNNAMED search for a named attribute matching @name. Otherwise,
+ * match both named and unnamed attributes.
+ *
+ * If @ic is IGNORE_CASE, the @name comparison is not case sensitive and
+ * @ctx->ntfs_ino must be set to the ntfs inode to which the mft record
+ * @ctx->mrec belongs. This is so we can get at the ntfs volume and hence at
+ * the upcase table. If @ic is CASE_SENSITIVE, the comparison is case
+ * sensitive. When @name is present, @name_len is the @name length in Unicode
+ * characters.
+ *
+ * If @name is not present (NULL), we assume that the unnamed attribute is
+ * being searched for.
+ *
+ * Finally, the resident attribute value @val is looked for, if present.
+ * If @val is not present (NULL), @val_len is ignored.
+ *
+ * ntfs_attr_find() only searches the specified mft record and it ignores the
+ * presence of an attribute list attribute (unless it is the one being searched
+ * for, obviously). If you need to take attribute lists into consideration, use
+ * ntfs_attr_lookup() instead (see below). This also means that you cannot use
+ * ntfs_attr_find() to search for extent records of non-resident attributes, as
+ * extents with lowest_vcn != 0 are usually described by the attribute list
+ * attribute only. - Note that it is possible that the first extent is only in
+ * the attribute list while the last extent is in the base mft record, so don't
+ * rely on being able to find the first extent in the base mft record.
+ *
+ * Warning: Never use @val when looking for attribute types which can be
+ * non-resident as this most likely will result in a crash!
+ */
+static int ntfs_attr_find(const ATTR_TYPES type, const ntfschar *name,
+ const u32 name_len, const IGNORE_CASE_BOOL ic,
+ const u8 *val, const u32 val_len, ntfs_attr_search_ctx *ctx)
+{
+ ATTR_RECORD *a;
+ ntfs_volume *vol;
+ ntfschar *upcase;
+ u32 upcase_len;
+
+ ntfs_log_trace("attribute type 0x%x.\n", type);
+
+ if (ctx->ntfs_ino) {
+ vol = ctx->ntfs_ino->vol;
+ upcase = vol->upcase;
+ upcase_len = vol->upcase_len;
+ } else {
+ if (name && name != AT_UNNAMED) {
+ errno = EINVAL;
+ ntfs_log_perror("%s", __FUNCTION__);
+ return -1;
+ }
+ vol = NULL;
+ upcase = NULL;
+ upcase_len = 0;
+ }
+ /*
+ * Iterate over attributes in mft record starting at @ctx->attr, or the
+ * attribute following that, if @ctx->is_first is TRUE.
+ */
+ if (ctx->is_first) {
+ a = ctx->attr;
+ ctx->is_first = FALSE;
+ } else
+ a = (ATTR_RECORD*)((char*)ctx->attr +
+ le32_to_cpu(ctx->attr->length));
+ for (;; a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length))) {
+ if (p2n(a) < p2n(ctx->mrec) || (char*)a > (char*)ctx->mrec +
+ le32_to_cpu(ctx->mrec->bytes_allocated))
+ break;
+ ctx->attr = a;
+ if (((type != AT_UNUSED) && (le32_to_cpu(a->type) >
+ le32_to_cpu(type))) ||
+ (a->type == AT_END)) {
+ errno = ENOENT;
+ return -1;
+ }
+ if (!a->length)
+ break;
+ /* If this is an enumeration return this attribute. */
+ if (type == AT_UNUSED)
+ return 0;
+ if (a->type != type)
+ continue;
+ /*
+ * If @name is AT_UNNAMED we want an unnamed attribute.
+ * If @name is present, compare the two names.
+ * Otherwise, match any attribute.
+ */
+ if (name == AT_UNNAMED) {
+ /* The search failed if the found attribute is named. */
+ if (a->name_length) {
+ errno = ENOENT;
+ return -1;
+ }
+ } else {
+ register int rc;
+ if (name && ((rc = ntfs_names_full_collate(name,
+ name_len, (ntfschar*)((char*)a +
+ le16_to_cpu(a->name_offset)),
+ a->name_length, ic,
+ upcase, upcase_len)))) {
+ /*
+ * If @name collates before a->name,
+ * there is no matching attribute.
+ */
+ if (rc < 0) {
+ errno = ENOENT;
+ return -1;
+ }
+ /* If the strings are not equal, continue search. */
+ continue;
+ }
+ }
+ /*
+ * The names match or @name not present and attribute is
+ * unnamed. If no @val specified, we have found the attribute
+ * and are done.
+ */
+ if (!val)
+ return 0;
+ /* @val is present; compare values. */
+ else {
+ register int rc;
+
+ rc = memcmp(val, (char*)a +le16_to_cpu(a->value_offset),
+ min(val_len,
+ le32_to_cpu(a->value_length)));
+ /*
+ * If @val collates before the current attribute's
+ * value, there is no matching attribute.
+ */
+ if (!rc) {
+ register u32 avl;
+ avl = le32_to_cpu(a->value_length);
+ if (val_len == avl)
+ return 0;
+ if (val_len < avl) {
+ errno = ENOENT;
+ return -1;
+ }
+ } else if (rc < 0) {
+ errno = ENOENT;
+ return -1;
+ }
+ }
+ }
+ errno = EIO;
+ ntfs_log_perror("%s: Corrupt inode (%lld)", __FUNCTION__,
+ ctx->ntfs_ino ? (long long)ctx->ntfs_ino->mft_no : -1);
+ return -1;
+}
+
+void ntfs_attr_name_free(char **name)
+{
+ if (*name) {
+ free(*name);
+ *name = NULL;
+ }
+}
+
+char *ntfs_attr_name_get(const ntfschar *uname, const int uname_len)
+{
+ char *name = NULL;
+ int name_len;
+
+ name_len = ntfs_ucstombs(uname, uname_len, &name, 0);
+ if (name_len < 0) {
+ ntfs_log_perror("ntfs_ucstombs");
+ return NULL;
+
+ } else if (name_len > 0)
+ return name;
+
+ ntfs_attr_name_free(&name);
+ return NULL;
+}
+
+/**
+ * ntfs_external_attr_find - find an attribute in the attribute list of an inode
+ * @type: attribute type to find
+ * @name: attribute name to find (optional, i.e. NULL means don't care)
+ * @name_len: attribute name length (only needed if @name present)
+ * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present)
+ * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only)
+ * @val: attribute value to find (optional, resident attributes only)
+ * @val_len: attribute value length
+ * @ctx: search context with mft record and attribute to search from
+ *
+ * You shouldn't need to call this function directly. Use ntfs_attr_lookup()
+ * instead.
+ *
+ * Find an attribute by searching the attribute list for the corresponding
+ * attribute list entry. Having found the entry, map the mft record for read
+ * if the attribute is in a different mft record/inode, find the attribute in
+ * there and return it.
+ *
+ * If @type is AT_UNUSED, return the first found attribute, i.e. one can
+ * enumerate all attributes by setting @type to AT_UNUSED and then calling
+ * ntfs_external_attr_find() repeatedly until it returns -1 with errno set to
+ * ENOENT to indicate that there are no more entries. During the enumeration,
+ * each successful call of ntfs_external_attr_find() will return the next
+ * attribute described by the attribute list of the base mft record described
+ * by the search context @ctx.
+ *
+ * If @type is AT_END, seek to the end of the base mft record ignoring the
+ * attribute list completely and return -1 with errno set to ENOENT. AT_END is
+ * not a valid attribute, its length is zero for example, thus it is safer to
+ * return error instead of success in this case.
+ *
+ * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present
+ * but not AT_UNNAMED search for a named attribute matching @name. Otherwise,
+ * match both named and unnamed attributes.
+ *
+ * On first search @ctx->ntfs_ino must be the inode of the base mft record and
+ * @ctx must have been obtained from a call to ntfs_attr_get_search_ctx().
+ * On subsequent calls, @ctx->ntfs_ino can be any extent inode, too
+ * (@ctx->base_ntfs_ino is then the base inode).
+ *
+ * After finishing with the attribute/mft record you need to call
+ * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any
+ * mapped extent inodes, etc).
+ *
+ * Return 0 if the search was successful and -1 if not, with errno set to the
+ * error code.
+ *
+ * On success, @ctx->attr is the found attribute, it is in mft record
+ * @ctx->mrec, and @ctx->al_entry is the attribute list entry for this
+ * attribute with @ctx->base_* being the base mft record to which @ctx->attr
+ * belongs.
+ *
+ * On error ENOENT, i.e. attribute not found, @ctx->attr is set to the
+ * attribute which collates just after the attribute being searched for in the
+ * base ntfs inode, i.e. if one wants to add the attribute to the mft record
+ * this is the correct place to insert it into, and if there is not enough
+ * space, the attribute should be placed in an extent mft record.
+ * @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list
+ * at which the new attribute's attribute list entry should be inserted. The
+ * other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL.
+ * The only exception to this is when @type is AT_END, in which case
+ * @ctx->al_entry is set to NULL also (see above).
+ *
+ * The following error codes are defined:
+ * ENOENT Attribute not found, not an error as such.
+ * EINVAL Invalid arguments.
+ * EIO I/O error or corrupt data structures found.
+ * ENOMEM Not enough memory to allocate necessary buffers.
+ */
+static int ntfs_external_attr_find(ATTR_TYPES type, const ntfschar *name,
+ const u32 name_len, const IGNORE_CASE_BOOL ic,
+ const VCN lowest_vcn, const u8 *val, const u32 val_len,
+ ntfs_attr_search_ctx *ctx)
+{
+ ntfs_inode *base_ni, *ni;
+ ntfs_volume *vol;
+ ATTR_LIST_ENTRY *al_entry, *next_al_entry;
+ u8 *al_start, *al_end;
+ ATTR_RECORD *a;
+ ntfschar *al_name;
+ u32 al_name_len;
+ BOOL is_first_search = FALSE;
+
+ ni = ctx->ntfs_ino;
+ base_ni = ctx->base_ntfs_ino;
+ ntfs_log_trace("Entering for inode %lld, attribute type 0x%x.\n",
+ (unsigned long long)ni->mft_no, type);
+ if (!base_ni) {
+ /* First call happens with the base mft record. */
+ base_ni = ctx->base_ntfs_ino = ctx->ntfs_ino;
+ ctx->base_mrec = ctx->mrec;
+ }
+ if (ni == base_ni)
+ ctx->base_attr = ctx->attr;
+ if (type == AT_END)
+ goto not_found;
+ vol = base_ni->vol;
+ al_start = base_ni->attr_list;
+ al_end = al_start + base_ni->attr_list_size;
+ if (!ctx->al_entry) {
+ ctx->al_entry = (ATTR_LIST_ENTRY*)al_start;
+ is_first_search = TRUE;
+ }
+ /*
+ * Iterate over entries in attribute list starting at @ctx->al_entry,
+ * or the entry following that, if @ctx->is_first is TRUE.
+ */
+ if (ctx->is_first) {
+ al_entry = ctx->al_entry;
+ ctx->is_first = FALSE;
+ /*
+ * If an enumeration and the first attribute is higher than
+ * the attribute list itself, need to return the attribute list
+ * attribute.
+ */
+ if ((type == AT_UNUSED) && is_first_search &&
+ le32_to_cpu(al_entry->type) >
+ le32_to_cpu(AT_ATTRIBUTE_LIST))
+ goto find_attr_list_attr;
+ } else {
+ al_entry = (ATTR_LIST_ENTRY*)((char*)ctx->al_entry +
+ le16_to_cpu(ctx->al_entry->length));
+ /*
+ * If this is an enumeration and the attribute list attribute
+ * is the next one in the enumeration sequence, just return the
+ * attribute list attribute from the base mft record as it is
+ * not listed in the attribute list itself.
+ */
+ if ((type == AT_UNUSED) && le32_to_cpu(ctx->al_entry->type) <
+ le32_to_cpu(AT_ATTRIBUTE_LIST) &&
+ le32_to_cpu(al_entry->type) >
+ le32_to_cpu(AT_ATTRIBUTE_LIST)) {
+ int rc;
+find_attr_list_attr:
+
+ /* Check for bogus calls. */
+ if (name || name_len || val || val_len || lowest_vcn) {
+ errno = EINVAL;
+ ntfs_log_perror("%s", __FUNCTION__);
+ return -1;
+ }
+
+ /* We want the base record. */
+ ctx->ntfs_ino = base_ni;
+ ctx->mrec = ctx->base_mrec;
+ ctx->is_first = TRUE;
+ /* Sanity checks are performed elsewhere. */
+ ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec +
+ le16_to_cpu(ctx->mrec->attrs_offset));
+
+ /* Find the attribute list attribute. */
+ rc = ntfs_attr_find(AT_ATTRIBUTE_LIST, NULL, 0,
+ IGNORE_CASE, NULL, 0, ctx);
+
+ /*
+ * Setup the search context so the correct
+ * attribute is returned next time round.
+ */
+ ctx->al_entry = al_entry;
+ ctx->is_first = TRUE;
+
+ /* Got it. Done. */
+ if (!rc)
+ return 0;
+
+ /* Error! If other than not found return it. */
+ if (errno != ENOENT)
+ return rc;
+
+ /* Not found?!? Absurd! */
+ errno = EIO;
+ ntfs_log_error("Attribute list wasn't found");
+ return -1;
+ }
+ }
+ for (;; al_entry = next_al_entry) {
+ /* Out of bounds check. */
+ if ((u8*)al_entry < base_ni->attr_list ||
+ (u8*)al_entry > al_end)
+ break; /* Inode is corrupt. */
+ ctx->al_entry = al_entry;
+ /* Catch the end of the attribute list. */
+ if ((u8*)al_entry == al_end)
+ goto not_found;
+ if (!al_entry->length)
+ break;
+ if ((u8*)al_entry + 6 > al_end || (u8*)al_entry +
+ le16_to_cpu(al_entry->length) > al_end)
+ break;
+ next_al_entry = (ATTR_LIST_ENTRY*)((u8*)al_entry +
+ le16_to_cpu(al_entry->length));
+ if (type != AT_UNUSED) {
+ if (le32_to_cpu(al_entry->type) > le32_to_cpu(type))
+ goto not_found;
+ if (type != al_entry->type)
+ continue;
+ }
+ al_name_len = al_entry->name_length;
+ al_name = (ntfschar*)((u8*)al_entry + al_entry->name_offset);
+ /*
+ * If !@type we want the attribute represented by this
+ * attribute list entry.
+ */
+ if (type == AT_UNUSED)
+ goto is_enumeration;
+ /*
+ * If @name is AT_UNNAMED we want an unnamed attribute.
+ * If @name is present, compare the two names.
+ * Otherwise, match any attribute.
+ */
+ if (name == AT_UNNAMED) {
+ if (al_name_len)
+ goto not_found;
+ } else {
+ int rc;
+
+ if (name && ((rc = ntfs_names_full_collate(name,
+ name_len, al_name, al_name_len, ic,
+ vol->upcase, vol->upcase_len)))) {
+
+ /*
+ * If @name collates before al_name,
+ * there is no matching attribute.
+ */
+ if (rc < 0)
+ goto not_found;
+ /* If the strings are not equal, continue search. */
+ continue;
+ }
+ }
+ /*
+ * The names match or @name not present and attribute is
+ * unnamed. Now check @lowest_vcn. Continue search if the
+ * next attribute list entry still fits @lowest_vcn. Otherwise
+ * we have reached the right one or the search has failed.
+ */
+ if (lowest_vcn && (u8*)next_al_entry >= al_start &&
+ (u8*)next_al_entry + 6 < al_end &&
+ (u8*)next_al_entry + le16_to_cpu(
+ next_al_entry->length) <= al_end &&
+ sle64_to_cpu(next_al_entry->lowest_vcn) <=
+ lowest_vcn &&
+ next_al_entry->type == al_entry->type &&
+ next_al_entry->name_length == al_name_len &&
+ ntfs_names_are_equal((ntfschar*)((char*)
+ next_al_entry +
+ next_al_entry->name_offset),
+ next_al_entry->name_length,
+ al_name, al_name_len, CASE_SENSITIVE,
+ vol->upcase, vol->upcase_len))
+ continue;
+is_enumeration:
+ if (MREF_LE(al_entry->mft_reference) == ni->mft_no) {
+ if (MSEQNO_LE(al_entry->mft_reference) !=
+ le16_to_cpu(
+ ni->mrec->sequence_number)) {
+ ntfs_log_error("Found stale mft reference in "
+ "attribute list!\n");
+ break;
+ }
+ } else { /* Mft references do not match. */
+ /* Do we want the base record back? */
+ if (MREF_LE(al_entry->mft_reference) ==
+ base_ni->mft_no) {
+ ni = ctx->ntfs_ino = base_ni;
+ ctx->mrec = ctx->base_mrec;
+ } else {
+ /* We want an extent record. */
+ ni = ntfs_extent_inode_open(base_ni,
+ al_entry->mft_reference);
+ if (!ni)
+ break;
+ ctx->ntfs_ino = ni;
+ ctx->mrec = ni->mrec;
+ }
+ }
+ a = ctx->attr = (ATTR_RECORD*)((char*)ctx->mrec +
+ le16_to_cpu(ctx->mrec->attrs_offset));
+ /*
+ * ctx->ntfs_ino, ctx->mrec, and ctx->attr now point to the
+ * mft record containing the attribute represented by the
+ * current al_entry.
+ *
+ * We could call into ntfs_attr_find() to find the right
+ * attribute in this mft record but this would be less
+ * efficient and not quite accurate as ntfs_attr_find() ignores
+ * the attribute instance numbers for example which become
+ * important when one plays with attribute lists. Also, because
+ * a proper match has been found in the attribute list entry
+ * above, the comparison can now be optimized. So it is worth
+ * re-implementing a simplified ntfs_attr_find() here.
+ *
+ * Use a manual loop so we can still use break and continue
+ * with the same meanings as above.
+ */
+do_next_attr_loop:
+ if ((char*)a < (char*)ctx->mrec || (char*)a > (char*)ctx->mrec +
+ le32_to_cpu(ctx->mrec->bytes_allocated))
+ break;
+ if (a->type == AT_END)
+ continue;
+ if (!a->length)
+ break;
+ if (al_entry->instance != a->instance)
+ goto do_next_attr;
+ /*
+ * If the type and/or the name are/is mismatched between the
+ * attribute list entry and the attribute record, there is
+ * corruption so we break and return error EIO.
+ */
+ if (al_entry->type != a->type)
+ break;
+ if (!ntfs_names_are_equal((ntfschar*)((char*)a +
+ le16_to_cpu(a->name_offset)),
+ a->name_length, al_name,
+ al_name_len, CASE_SENSITIVE,
+ vol->upcase, vol->upcase_len))
+ break;
+ ctx->attr = a;
+ /*
+ * If no @val specified or @val specified and it matches, we
+ * have found it! Also, if !@type, it is an enumeration, so we
+ * want the current attribute.
+ */
+ if ((type == AT_UNUSED) || !val || (!a->non_resident &&
+ le32_to_cpu(a->value_length) == val_len &&
+ !memcmp((char*)a + le16_to_cpu(a->value_offset),
+ val, val_len))) {
+ return 0;
+ }
+do_next_attr:
+ /* Proceed to the next attribute in the current mft record. */
+ a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length));
+ goto do_next_attr_loop;
+ }
+ if (ni != base_ni) {
+ ctx->ntfs_ino = base_ni;
+ ctx->mrec = ctx->base_mrec;
+ ctx->attr = ctx->base_attr;
+ }
+ errno = EIO;
+ ntfs_log_perror("Inode is corrupt (%lld)", (long long)base_ni->mft_no);
+ return -1;
+not_found:
+ /*
+ * If we were looking for AT_END or we were enumerating and reached the
+ * end, we reset the search context @ctx and use ntfs_attr_find() to
+ * seek to the end of the base mft record.
+ */
+ if (type == AT_UNUSED || type == AT_END) {
+ ntfs_attr_reinit_search_ctx(ctx);
+ return ntfs_attr_find(AT_END, name, name_len, ic, val, val_len,
+ ctx);
+ }
+ /*
+ * The attribute wasn't found. Before we return, we want to ensure
+ * @ctx->mrec and @ctx->attr indicate the position at which the
+ * attribute should be inserted in the base mft record. Since we also
+ * want to preserve @ctx->al_entry we cannot reinitialize the search
+ * context using ntfs_attr_reinit_search_ctx() as this would set
+ * @ctx->al_entry to NULL. Thus we do the necessary bits manually (see
+ * ntfs_attr_init_search_ctx() below). Note, we _only_ preserve
+ * @ctx->al_entry as the remaining fields (base_*) are identical to
+ * their non base_ counterparts and we cannot set @ctx->base_attr
+ * correctly yet as we do not know what @ctx->attr will be set to by
+ * the call to ntfs_attr_find() below.
+ */
+ ctx->mrec = ctx->base_mrec;
+ ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec +
+ le16_to_cpu(ctx->mrec->attrs_offset));
+ ctx->is_first = TRUE;
+ ctx->ntfs_ino = ctx->base_ntfs_ino;
+ ctx->base_ntfs_ino = NULL;
+ ctx->base_mrec = NULL;
+ ctx->base_attr = NULL;
+ /*
+ * In case there are multiple matches in the base mft record, need to
+ * keep enumerating until we get an attribute not found response (or
+ * another error), otherwise we would keep returning the same attribute
+ * over and over again and all programs using us for enumeration would
+ * lock up in a tight loop.
+ */
+ {
+ int ret;
+
+ do {
+ ret = ntfs_attr_find(type, name, name_len, ic, val,
+ val_len, ctx);
+ } while (!ret);
+ return ret;
+ }
+}
+
+/**
+ * ntfs_attr_lookup - find an attribute in an ntfs inode
+ * @type: attribute type to find
+ * @name: attribute name to find (optional, i.e. NULL means don't care)
+ * @name_len: attribute name length (only needed if @name present)
+ * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present)
+ * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only)
+ * @val: attribute value to find (optional, resident attributes only)
+ * @val_len: attribute value length
+ * @ctx: search context with mft record and attribute to search from
+ *
+ * Find an attribute in an ntfs inode. On first search @ctx->ntfs_ino must
+ * be the base mft record and @ctx must have been obtained from a call to
+ * ntfs_attr_get_search_ctx().
+ *
+ * This function transparently handles attribute lists and @ctx is used to
+ * continue searches where they were left off at.
+ *
+ * If @type is AT_UNUSED, return the first found attribute, i.e. one can
+ * enumerate all attributes by setting @type to AT_UNUSED and then calling
+ * ntfs_attr_lookup() repeatedly until it returns -1 with errno set to ENOENT
+ * to indicate that there are no more entries. During the enumeration, each
+ * successful call of ntfs_attr_lookup() will return the next attribute, with
+ * the current attribute being described by the search context @ctx.
+ *
+ * If @type is AT_END, seek to the end of the base mft record ignoring the
+ * attribute list completely and return -1 with errno set to ENOENT. AT_END is
+ * not a valid attribute, its length is zero for example, thus it is safer to
+ * return error instead of success in this case. It should never be needed to
+ * do this, but we implement the functionality because it allows for simpler
+ * code inside ntfs_external_attr_find().
+ *
+ * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present
+ * but not AT_UNNAMED search for a named attribute matching @name. Otherwise,
+ * match both named and unnamed attributes.
+ *
+ * After finishing with the attribute/mft record you need to call
+ * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any
+ * mapped extent inodes, etc).
+ *
+ * Return 0 if the search was successful and -1 if not, with errno set to the
+ * error code.
+ *
+ * On success, @ctx->attr is the found attribute, it is in mft record
+ * @ctx->mrec, and @ctx->al_entry is the attribute list entry for this
+ * attribute with @ctx->base_* being the base mft record to which @ctx->attr
+ * belongs. If no attribute list attribute is present @ctx->al_entry and
+ * @ctx->base_* are NULL.
+ *
+ * On error ENOENT, i.e. attribute not found, @ctx->attr is set to the
+ * attribute which collates just after the attribute being searched for in the
+ * base ntfs inode, i.e. if one wants to add the attribute to the mft record
+ * this is the correct place to insert it into, and if there is not enough
+ * space, the attribute should be placed in an extent mft record.
+ * @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list
+ * at which the new attribute's attribute list entry should be inserted. The
+ * other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL.
+ * The only exception to this is when @type is AT_END, in which case
+ * @ctx->al_entry is set to NULL also (see above).
+ *
+ *
+ * The following error codes are defined:
+ * ENOENT Attribute not found, not an error as such.
+ * EINVAL Invalid arguments.
+ * EIO I/O error or corrupt data structures found.
+ * ENOMEM Not enough memory to allocate necessary buffers.
+ */
+int ntfs_attr_lookup(const ATTR_TYPES type, const ntfschar *name,
+ const u32 name_len, const IGNORE_CASE_BOOL ic,
+ const VCN lowest_vcn, const u8 *val, const u32 val_len,
+ ntfs_attr_search_ctx *ctx)
+{
+ ntfs_volume *vol;
+ ntfs_inode *base_ni;
+ int ret = -1;
+
+ ntfs_log_enter("Entering for attribute type 0x%x\n", type);
+
+ if (!ctx || !ctx->mrec || !ctx->attr || (name && name != AT_UNNAMED &&
+ (!ctx->ntfs_ino || !(vol = ctx->ntfs_ino->vol) ||
+ !vol->upcase || !vol->upcase_len))) {
+ errno = EINVAL;
+ ntfs_log_perror("%s", __FUNCTION__);
+ goto out;
+ }
+
+ if (ctx->base_ntfs_ino)
+ base_ni = ctx->base_ntfs_ino;
+ else
+ base_ni = ctx->ntfs_ino;
+ if (!base_ni || !NInoAttrList(base_ni) || type == AT_ATTRIBUTE_LIST)
+ ret = ntfs_attr_find(type, name, name_len, ic, val, val_len, ctx);
+ else
+ ret = ntfs_external_attr_find(type, name, name_len, ic,
+ lowest_vcn, val, val_len, ctx);
+out:
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+/**
+ * ntfs_attr_position - find given or next attribute type in an ntfs inode
+ * @type: attribute type to start lookup
+ * @ctx: search context with mft record and attribute to search from
+ *
+ * Find an attribute type in an ntfs inode or the next attribute which is not
+ * the AT_END attribute. Please see more details at ntfs_attr_lookup.
+ *
+ * Return 0 if the search was successful and -1 if not, with errno set to the
+ * error code.
+ *
+ * The following error codes are defined:
+ * EINVAL Invalid arguments.
+ * EIO I/O error or corrupt data structures found.
+ * ENOMEM Not enough memory to allocate necessary buffers.
+ * ENOSPC No attribute was found after 'type', only AT_END.
+ */
+int ntfs_attr_position(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx)
+{
+ if (ntfs_attr_lookup(type, NULL, 0, CASE_SENSITIVE, 0, NULL, 0, ctx)) {
+ if (errno != ENOENT)
+ return -1;
+ if (ctx->attr->type == AT_END) {
+ errno = ENOSPC;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ntfs_attr_init_search_ctx - initialize an attribute search context
+ * @ctx: attribute search context to initialize
+ * @ni: ntfs inode with which to initialize the search context
+ * @mrec: mft record with which to initialize the search context
+ *
+ * Initialize the attribute search context @ctx with @ni and @mrec.
+ */
+static void ntfs_attr_init_search_ctx(ntfs_attr_search_ctx *ctx,
+ ntfs_inode *ni, MFT_RECORD *mrec)
+{
+ if (!mrec)
+ mrec = ni->mrec;
+ ctx->mrec = mrec;
+ /* Sanity checks are performed elsewhere. */
+ ctx->attr = (ATTR_RECORD*)((u8*)mrec + le16_to_cpu(mrec->attrs_offset));
+ ctx->is_first = TRUE;
+ ctx->ntfs_ino = ni;
+ ctx->al_entry = NULL;
+ ctx->base_ntfs_ino = NULL;
+ ctx->base_mrec = NULL;
+ ctx->base_attr = NULL;
+}
+
+/**
+ * ntfs_attr_reinit_search_ctx - reinitialize an attribute search context
+ * @ctx: attribute search context to reinitialize
+ *
+ * Reinitialize the attribute search context @ctx.
+ *
+ * This is used when a search for a new attribute is being started to reset
+ * the search context to the beginning.
+ */
+void ntfs_attr_reinit_search_ctx(ntfs_attr_search_ctx *ctx)
+{
+ if (!ctx->base_ntfs_ino) {
+ /* No attribute list. */
+ ctx->is_first = TRUE;
+ /* Sanity checks are performed elsewhere. */
+ ctx->attr = (ATTR_RECORD*)((u8*)ctx->mrec +
+ le16_to_cpu(ctx->mrec->attrs_offset));
+ /*
+ * This needs resetting due to ntfs_external_attr_find() which
+ * can leave it set despite having zeroed ctx->base_ntfs_ino.
+ */
+ ctx->al_entry = NULL;
+ return;
+ } /* Attribute list. */
+ ntfs_attr_init_search_ctx(ctx, ctx->base_ntfs_ino, ctx->base_mrec);
+ return;
+}
+
+/**
+ * ntfs_attr_get_search_ctx - allocate/initialize a new attribute search context
+ * @ni: ntfs inode with which to initialize the search context
+ * @mrec: mft record with which to initialize the search context
+ *
+ * Allocate a new attribute search context, initialize it with @ni and @mrec,
+ * and return it. Return NULL on error with errno set.
+ *
+ * @mrec can be NULL, in which case the mft record is taken from @ni.
+ *
+ * Note: For low level utilities which know what they are doing we allow @ni to
+ * be NULL and @mrec to be set. Do NOT do this unless you understand the
+ * implications!!! For example it is no longer safe to call ntfs_attr_lookup().
+ */
+ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(ntfs_inode *ni, MFT_RECORD *mrec)
+{
+ ntfs_attr_search_ctx *ctx;
+
+ if (!ni && !mrec) {
+ errno = EINVAL;
+ ntfs_log_perror("NULL arguments");
+ return NULL;
+ }
+ ctx = ntfs_malloc(sizeof(ntfs_attr_search_ctx));
+ if (ctx)
+ ntfs_attr_init_search_ctx(ctx, ni, mrec);
+ return ctx;
+}
+
+/**
+ * ntfs_attr_put_search_ctx - release an attribute search context
+ * @ctx: attribute search context to free
+ *
+ * Release the attribute search context @ctx.
+ */
+void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx)
+{
+ // NOTE: save errno if it could change and function stays void!
+ free(ctx);
+}
+
+/**
+ * ntfs_attr_find_in_attrdef - find an attribute in the $AttrDef system file
+ * @vol: ntfs volume to which the attribute belongs
+ * @type: attribute type which to find
+ *
+ * Search for the attribute definition record corresponding to the attribute
+ * @type in the $AttrDef system file.
+ *
+ * Return the attribute type definition record if found and NULL if not found
+ * or an error occurred. On error the error code is stored in errno. The
+ * following error codes are defined:
+ * ENOENT - The attribute @type is not specified in $AttrDef.
+ * EINVAL - Invalid parameters (e.g. @vol is not valid).
+ */
+ATTR_DEF *ntfs_attr_find_in_attrdef(const ntfs_volume *vol,
+ const ATTR_TYPES type)
+{
+ ATTR_DEF *ad;
+
+ if (!vol || !vol->attrdef || !type) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: type=%d", __FUNCTION__, type);
+ return NULL;
+ }
+ for (ad = vol->attrdef; (u8*)ad - (u8*)vol->attrdef <
+ vol->attrdef_len && ad->type; ++ad) {
+ /* We haven't found it yet, carry on searching. */
+ if (le32_to_cpu(ad->type) < le32_to_cpu(type))
+ continue;
+ /* We found the attribute; return it. */
+ if (ad->type == type)
+ return ad;
+ /* We have gone too far already. No point in continuing. */
+ break;
+ }
+ errno = ENOENT;
+ ntfs_log_perror("%s: type=%d", __FUNCTION__, type);
+ return NULL;
+}
+
+/**
+ * ntfs_attr_size_bounds_check - check a size of an attribute type for validity
+ * @vol: ntfs volume to which the attribute belongs
+ * @type: attribute type which to check
+ * @size: size which to check
+ *
+ * Check whether the @size in bytes is valid for an attribute of @type on the
+ * ntfs volume @vol. This information is obtained from $AttrDef system file.
+ *
+ * Return 0 if valid and -1 if not valid or an error occurred. On error the
+ * error code is stored in errno. The following error codes are defined:
+ * ERANGE - @size is not valid for the attribute @type.
+ * ENOENT - The attribute @type is not specified in $AttrDef.
+ * EINVAL - Invalid parameters (e.g. @size is < 0 or @vol is not valid).
+ */
+int ntfs_attr_size_bounds_check(const ntfs_volume *vol, const ATTR_TYPES type,
+ const s64 size)
+{
+ ATTR_DEF *ad;
+ s64 min_size, max_size;
+
+ if (size < 0) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: size=%lld", __FUNCTION__,
+ (long long)size);
+ return -1;
+ }
+
+ /*
+ * $ATTRIBUTE_LIST shouldn't be greater than 0x40000, otherwise
+ * Windows would crash. This is not listed in the AttrDef.
+ */
+ if (type == AT_ATTRIBUTE_LIST && size > 0x40000) {
+ errno = ERANGE;
+ ntfs_log_perror("Too large attrlist (%lld)", (long long)size);
+ return -1;
+ }
+
+ ad = ntfs_attr_find_in_attrdef(vol, type);
+ if (!ad)
+ return -1;
+
+ min_size = sle64_to_cpu(ad->min_size);
+ max_size = sle64_to_cpu(ad->max_size);
+
+ if ((min_size && (size < min_size)) ||
+ ((max_size > 0) && (size > max_size))) {
+ errno = ERANGE;
+ ntfs_log_perror("Attr type %d size check failed (min,size,max="
+ "%lld,%lld,%lld)", type, (long long)min_size,
+ (long long)size, (long long)max_size);
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * ntfs_attr_can_be_non_resident - check if an attribute can be non-resident
+ * @vol: ntfs volume to which the attribute belongs
+ * @type: attribute type to check
+ * @name: attribute name to check
+ * @name_len: attribute name length
+ *
+ * Check whether the attribute of @type and @name with name length @name_len on
+ * the ntfs volume @vol is allowed to be non-resident. This information is
+ * obtained from $AttrDef system file and is augmented by rules imposed by
+ * Microsoft (e.g. see http://support.microsoft.com/kb/974729/).
+ *
+ * Return 0 if the attribute is allowed to be non-resident and -1 if not or an
+ * error occurred. On error the error code is stored in errno. The following
+ * error codes are defined:
+ * EPERM - The attribute is not allowed to be non-resident.
+ * ENOENT - The attribute @type is not specified in $AttrDef.
+ * EINVAL - Invalid parameters (e.g. @vol is not valid).
+ */
+static int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, const ATTR_TYPES type,
+ const ntfschar *name, int name_len)
+{
+ ATTR_DEF *ad;
+ BOOL allowed;
+
+ /*
+ * Microsoft has decreed that $LOGGED_UTILITY_STREAM attributes with a
+ * name of $TXF_DATA must be resident despite the entry for
+ * $LOGGED_UTILITY_STREAM in $AttrDef allowing them to be non-resident.
+ * Failure to obey this on the root directory mft record of a volume
+ * causes Windows Vista and later to see the volume as a RAW volume and
+ * thus cannot mount it at all.
+ */
+ if ((type == AT_LOGGED_UTILITY_STREAM)
+ && name
+ && ntfs_names_are_equal(TXF_DATA, 9, name, name_len,
+ CASE_SENSITIVE, vol->upcase, vol->upcase_len))
+ allowed = FALSE;
+ else {
+ /* Find the attribute definition record in $AttrDef. */
+ ad = ntfs_attr_find_in_attrdef(vol, type);
+ if (!ad)
+ return -1;
+ /* Check the flags and return the result. */
+ allowed = !(ad->flags & ATTR_DEF_RESIDENT);
+ }
+ if (!allowed) {
+ errno = EPERM;
+ ntfs_log_trace("Attribute can't be non-resident\n");
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * ntfs_attr_can_be_resident - check if an attribute can be resident
+ * @vol: ntfs volume to which the attribute belongs
+ * @type: attribute type which to check
+ *
+ * Check whether the attribute of @type on the ntfs volume @vol is allowed to
+ * be resident. This information is derived from our ntfs knowledge and may
+ * not be completely accurate, especially when user defined attributes are
+ * present. Basically we allow everything to be resident except for index
+ * allocation and extended attribute attributes.
+ *
+ * Return 0 if the attribute is allowed to be resident and -1 if not or an
+ * error occurred. On error the error code is stored in errno. The following
+ * error codes are defined:
+ * EPERM - The attribute is not allowed to be resident.
+ * EINVAL - Invalid parameters (e.g. @vol is not valid).
+ *
+ * Warning: In the system file $MFT the attribute $Bitmap must be non-resident
+ * otherwise windows will not boot (blue screen of death)! We cannot
+ * check for this here as we don't know which inode's $Bitmap is being
+ * asked about so the caller needs to special case this.
+ */
+int ntfs_attr_can_be_resident(const ntfs_volume *vol, const ATTR_TYPES type)
+{
+ if (!vol || !vol->attrdef || !type) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (type != AT_INDEX_ALLOCATION)
+ return 0;
+
+ ntfs_log_trace("Attribute can't be resident\n");
+ errno = EPERM;
+ return -1;
+}
+
+/**
+ * ntfs_make_room_for_attr - make room for an attribute inside an mft record
+ * @m: mft record
+ * @pos: position at which to make space
+ * @size: byte size to make available at this position
+ *
+ * @pos points to the attribute in front of which we want to make space.
+ *
+ * Return 0 on success or -1 on error. On error the error code is stored in
+ * errno. Possible error codes are:
+ * ENOSPC - There is not enough space available to complete operation. The
+ * caller has to make space before calling this.
+ * EINVAL - Input parameters were faulty.
+ */
+int ntfs_make_room_for_attr(MFT_RECORD *m, u8 *pos, u32 size)
+{
+ u32 biu;
+
+ ntfs_log_trace("Entering for pos 0x%d, size %u.\n",
+ (int)(pos - (u8*)m), (unsigned) size);
+
+ /* Make size 8-byte alignment. */
+ size = (size + 7) & ~7;
+
+ /* Rigorous consistency checks. */
+ if (!m || !pos || pos < (u8*)m) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: pos=%p m=%p", __FUNCTION__, pos, m);
+ return -1;
+ }
+ /* The -8 is for the attribute terminator. */
+ if (pos - (u8*)m > (int)le32_to_cpu(m->bytes_in_use) - 8) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* Nothing to do. */
+ if (!size)
+ return 0;
+
+ biu = le32_to_cpu(m->bytes_in_use);
+ /* Do we have enough space? */
+ if (biu + size > le32_to_cpu(m->bytes_allocated) ||
+ pos + size > (u8*)m + le32_to_cpu(m->bytes_allocated)) {
+ errno = ENOSPC;
+ ntfs_log_trace("No enough space in the MFT record\n");
+ return -1;
+ }
+ /* Move everything after pos to pos + size. */
+ memmove(pos + size, pos, biu - (pos - (u8*)m));
+ /* Update mft record. */
+ m->bytes_in_use = cpu_to_le32(biu + size);
+ return 0;
+}
+
+/**
+ * ntfs_resident_attr_record_add - add resident attribute to inode
+ * @ni: opened ntfs inode to which MFT record add attribute
+ * @type: type of the new attribute
+ * @name: name of the new attribute
+ * @name_len: name length of the new attribute
+ * @val: value of the new attribute
+ * @size: size of new attribute (length of @val, if @val != NULL)
+ * @flags: flags of the new attribute
+ *
+ * Return offset to attribute from the beginning of the mft record on success
+ * and -1 on error. On error the error code is stored in errno.
+ * Possible error codes are:
+ * EINVAL - Invalid arguments passed to function.
+ * EEXIST - Attribute of such type and with same name already exists.
+ * EIO - I/O error occurred or damaged filesystem.
+ */
+int ntfs_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type,
+ ntfschar *name, u8 name_len, u8 *val, u32 size,
+ ATTR_FLAGS data_flags)
+{
+ ntfs_attr_search_ctx *ctx;
+ u32 length;
+ ATTR_RECORD *a;
+ MFT_RECORD *m;
+ int err, offset;
+ ntfs_inode *base_ni;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, flags 0x%x.\n",
+ (long long) ni->mft_no, (unsigned) type, (unsigned) data_flags);
+
+ if (!ni || (!name && name_len)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ntfs_attr_can_be_resident(ni->vol, type)) {
+ if (errno == EPERM)
+ ntfs_log_trace("Attribute can't be resident.\n");
+ else
+ ntfs_log_trace("ntfs_attr_can_be_resident failed.\n");
+ return -1;
+ }
+
+ /* Locate place where record should be. */
+ ctx = ntfs_attr_get_search_ctx(ni, NULL);
+ if (!ctx)
+ return -1;
+ /*
+ * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
+ * attribute in @ni->mrec, not any extent inode in case if @ni is base
+ * file record.
+ */
+ if (!ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, val, size,
+ ctx)) {
+ err = EEXIST;
+ ntfs_log_trace("Attribute already present.\n");
+ goto put_err_out;
+ }
+ if (errno != ENOENT) {
+ err = EIO;
+ goto put_err_out;
+ }
+ a = ctx->attr;
+ m = ctx->mrec;
+
+ /* Make room for attribute. */
+ length = offsetof(ATTR_RECORD, resident_end) +
+ ((name_len * sizeof(ntfschar) + 7) & ~7) +
+ ((size + 7) & ~7);
+ if (ntfs_make_room_for_attr(ctx->mrec, (u8*) ctx->attr, length)) {
+ err = errno;
+ ntfs_log_trace("Failed to make room for attribute.\n");
+ goto put_err_out;
+ }
+
+ /* Setup record fields. */
+ offset = ((u8*)a - (u8*)m);
+ a->type = type;
+ a->length = cpu_to_le32(length);
+ a->non_resident = 0;
+ a->name_length = name_len;
+ a->name_offset = (name_len
+ ? cpu_to_le16(offsetof(ATTR_RECORD, resident_end))
+ : const_cpu_to_le16(0));
+ a->flags = data_flags;
+ a->instance = m->next_attr_instance;
+ a->value_length = cpu_to_le32(size);
+ a->value_offset = cpu_to_le16(length - ((size + 7) & ~7));
+ if (val)
+ memcpy((u8*)a + le16_to_cpu(a->value_offset), val, size);
+ else
+ memset((u8*)a + le16_to_cpu(a->value_offset), 0, size);
+ if (type == AT_FILE_NAME)
+ a->resident_flags = RESIDENT_ATTR_IS_INDEXED;
+ else
+ a->resident_flags = 0;
+ if (name_len)
+ memcpy((u8*)a + le16_to_cpu(a->name_offset),
+ name, sizeof(ntfschar) * name_len);
+ m->next_attr_instance =
+ cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff);
+ if (ni->nr_extents == -1)
+ base_ni = ni->base_ni;
+ else
+ base_ni = ni;
+ if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) {
+ if (ntfs_attrlist_entry_add(ni, a)) {
+ err = errno;
+ ntfs_attr_record_resize(m, a, 0);
+ ntfs_log_trace("Failed add attribute entry to "
+ "ATTRIBUTE_LIST.\n");
+ goto put_err_out;
+ }
+ }
+ if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
+ ? type == AT_INDEX_ROOT && name == NTFS_INDEX_I30
+ : type == AT_DATA && name == AT_UNNAMED) {
+ ni->data_size = size;
+ ni->allocated_size = (size + 7) & ~7;
+ set_nino_flag(ni,KnownSize);
+ }
+ ntfs_inode_mark_dirty(ni);
+ ntfs_attr_put_search_ctx(ctx);
+ return offset;
+put_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+ errno = err;
+ return -1;
+}
+
+/**
+ * ntfs_non_resident_attr_record_add - add extent of non-resident attribute
+ * @ni: opened ntfs inode to which MFT record add attribute
+ * @type: type of the new attribute extent
+ * @name: name of the new attribute extent
+ * @name_len: name length of the new attribute extent
+ * @lowest_vcn: lowest vcn of the new attribute extent
+ * @dataruns_size: dataruns size of the new attribute extent
+ * @flags: flags of the new attribute extent
+ *
+ * Return offset to attribute from the beginning of the mft record on success
+ * and -1 on error. On error the error code is stored in errno.
+ * Possible error codes are:
+ * EINVAL - Invalid arguments passed to function.
+ * EEXIST - Attribute of such type, with same lowest vcn and with same
+ * name already exists.
+ * EIO - I/O error occurred or damaged filesystem.
+ */
+int ntfs_non_resident_attr_record_add(ntfs_inode *ni, ATTR_TYPES type,
+ ntfschar *name, u8 name_len, VCN lowest_vcn, int dataruns_size,
+ ATTR_FLAGS flags)
+{
+ ntfs_attr_search_ctx *ctx;
+ u32 length;
+ ATTR_RECORD *a;
+ MFT_RECORD *m;
+ ntfs_inode *base_ni;
+ int err, offset;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld, "
+ "dataruns_size %d, flags 0x%x.\n",
+ (long long) ni->mft_no, (unsigned) type,
+ (long long) lowest_vcn, dataruns_size, (unsigned) flags);
+
+ if (!ni || dataruns_size <= 0 || (!name && name_len)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ntfs_attr_can_be_non_resident(ni->vol, type, name, name_len)) {
+ if (errno == EPERM)
+ ntfs_log_perror("Attribute can't be non resident");
+ else
+ ntfs_log_perror("ntfs_attr_can_be_non_resident failed");
+ return -1;
+ }
+
+ /* Locate place where record should be. */
+ ctx = ntfs_attr_get_search_ctx(ni, NULL);
+ if (!ctx)
+ return -1;
+ /*
+ * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
+ * attribute in @ni->mrec, not any extent inode in case if @ni is base
+ * file record.
+ */
+ if (!ntfs_attr_find(type, name, name_len, CASE_SENSITIVE, NULL, 0,
+ ctx)) {
+ err = EEXIST;
+ ntfs_log_perror("Attribute 0x%x already present", type);
+ goto put_err_out;
+ }
+ if (errno != ENOENT) {
+ ntfs_log_perror("ntfs_attr_find failed");
+ err = EIO;
+ goto put_err_out;
+ }
+ a = ctx->attr;
+ m = ctx->mrec;
+
+ /* Make room for attribute. */
+ dataruns_size = (dataruns_size + 7) & ~7;
+ length = offsetof(ATTR_RECORD, compressed_size) + ((sizeof(ntfschar) *
+ name_len + 7) & ~7) + dataruns_size +
+ ((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ?
+ sizeof(a->compressed_size) : 0);
+ if (ntfs_make_room_for_attr(ctx->mrec, (u8*) ctx->attr, length)) {
+ err = errno;
+ ntfs_log_perror("Failed to make room for attribute");
+ goto put_err_out;
+ }
+
+ /* Setup record fields. */
+ a->type = type;
+ a->length = cpu_to_le32(length);
+ a->non_resident = 1;
+ a->name_length = name_len;
+ a->name_offset = cpu_to_le16(offsetof(ATTR_RECORD, compressed_size) +
+ ((flags & (ATTR_IS_COMPRESSED | ATTR_IS_SPARSE)) ?
+ sizeof(a->compressed_size) : 0));
+ a->flags = flags;
+ a->instance = m->next_attr_instance;
+ a->lowest_vcn = cpu_to_sle64(lowest_vcn);
+ a->mapping_pairs_offset = cpu_to_le16(length - dataruns_size);
+ a->compression_unit = (flags & ATTR_IS_COMPRESSED)
+ ? STANDARD_COMPRESSION_UNIT : 0;
+ /* If @lowest_vcn == 0, than setup empty attribute. */
+ if (!lowest_vcn) {
+ a->highest_vcn = cpu_to_sle64(-1);
+ a->allocated_size = 0;
+ a->data_size = 0;
+ a->initialized_size = 0;
+ /* Set empty mapping pairs. */
+ *((u8*)a + le16_to_cpu(a->mapping_pairs_offset)) = 0;
+ }
+ if (name_len)
+ memcpy((u8*)a + le16_to_cpu(a->name_offset),
+ name, sizeof(ntfschar) * name_len);
+ m->next_attr_instance =
+ cpu_to_le16((le16_to_cpu(m->next_attr_instance) + 1) & 0xffff);
+ if (ni->nr_extents == -1)
+ base_ni = ni->base_ni;
+ else
+ base_ni = ni;
+ if (type != AT_ATTRIBUTE_LIST && NInoAttrList(base_ni)) {
+ if (ntfs_attrlist_entry_add(ni, a)) {
+ err = errno;
+ ntfs_log_perror("Failed add attr entry to attrlist");
+ ntfs_attr_record_resize(m, a, 0);
+ goto put_err_out;
+ }
+ }
+ ntfs_inode_mark_dirty(ni);
+ /*
+ * Locate offset from start of the MFT record where new attribute is
+ * placed. We need relookup it, because record maybe moved during
+ * update of attribute list.
+ */
+ ntfs_attr_reinit_search_ctx(ctx);
+ if (ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE,
+ lowest_vcn, NULL, 0, ctx)) {
+ ntfs_log_perror("%s: attribute lookup failed", __FUNCTION__);
+ ntfs_attr_put_search_ctx(ctx);
+ return -1;
+
+ }
+ offset = (u8*)ctx->attr - (u8*)ctx->mrec;
+ ntfs_attr_put_search_ctx(ctx);
+ return offset;
+put_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+ errno = err;
+ return -1;
+}
+
+/**
+ * ntfs_attr_record_rm - remove attribute extent
+ * @ctx: search context describing the attribute which should be removed
+ *
+ * If this function succeed, user should reinit search context if he/she wants
+ * use it anymore.
+ *
+ * Return 0 on success and -1 on error. On error the error code is stored in
+ * errno. Possible error codes are:
+ * EINVAL - Invalid arguments passed to function.
+ * EIO - I/O error occurred or damaged filesystem.
+ */
+int ntfs_attr_record_rm(ntfs_attr_search_ctx *ctx)
+{
+ ntfs_inode *base_ni, *ni;
+ ATTR_TYPES type;
+
+ if (!ctx || !ctx->ntfs_ino || !ctx->mrec || !ctx->attr) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
+ (long long) ctx->ntfs_ino->mft_no,
+ (unsigned) le32_to_cpu(ctx->attr->type));
+ type = ctx->attr->type;
+ ni = ctx->ntfs_ino;
+ if (ctx->base_ntfs_ino)
+ base_ni = ctx->base_ntfs_ino;
+ else
+ base_ni = ctx->ntfs_ino;
+
+ /* Remove attribute itself. */
+ if (ntfs_attr_record_resize(ctx->mrec, ctx->attr, 0)) {
+ ntfs_log_trace("Couldn't remove attribute record. Bug or damaged MFT "
+ "record.\n");
+ if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST)
+ if (ntfs_attrlist_entry_add(ni, ctx->attr))
+ ntfs_log_trace("Rollback failed. Leaving inconstant "
+ "metadata.\n");
+ errno = EIO;
+ return -1;
+ }
+ ntfs_inode_mark_dirty(ni);
+
+ /*
+ * Remove record from $ATTRIBUTE_LIST if present and we don't want
+ * delete $ATTRIBUTE_LIST itself.
+ */
+ if (NInoAttrList(base_ni) && type != AT_ATTRIBUTE_LIST) {
+ if (ntfs_attrlist_entry_rm(ctx)) {
+ ntfs_log_trace("Couldn't delete record from "
+ "$ATTRIBUTE_LIST.\n");
+ return -1;
+ }
+ }
+
+ /* Post $ATTRIBUTE_LIST delete setup. */
+ if (type == AT_ATTRIBUTE_LIST) {
+ if (NInoAttrList(base_ni) && base_ni->attr_list)
+ free(base_ni->attr_list);
+ base_ni->attr_list = NULL;
+ NInoClearAttrList(base_ni);
+ NInoAttrListClearDirty(base_ni);
+ }
+
+ /* Free MFT record, if it doesn't contain attributes. */
+ if (le32_to_cpu(ctx->mrec->bytes_in_use) -
+ le16_to_cpu(ctx->mrec->attrs_offset) == 8) {
+ if (ntfs_mft_record_free(ni->vol, ni)) {
+ // FIXME: We need rollback here.
+ ntfs_log_trace("Couldn't free MFT record.\n");
+ errno = EIO;
+ return -1;
+ }
+ /* Remove done if we freed base inode. */
+ if (ni == base_ni)
+ return 0;
+ }
+
+ if (type == AT_ATTRIBUTE_LIST || !NInoAttrList(base_ni))
+ return 0;
+
+ /* Remove attribute list if we don't need it any more. */
+ if (!ntfs_attrlist_need(base_ni)) {
+ ntfs_attr_reinit_search_ctx(ctx);
+ if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, CASE_SENSITIVE,
+ 0, NULL, 0, ctx)) {
+ /*
+ * FIXME: Should we succeed here? Definitely something
+ * goes wrong because NInoAttrList(base_ni) returned
+ * that we have got attribute list.
+ */
+ ntfs_log_trace("Couldn't find attribute list. Succeed "
+ "anyway.\n");
+ return 0;
+ }
+ /* Deallocate clusters. */
+ if (ctx->attr->non_resident) {
+ runlist *al_rl;
+
+ al_rl = ntfs_mapping_pairs_decompress(base_ni->vol,
+ ctx->attr, NULL);
+ if (!al_rl) {
+ ntfs_log_trace("Couldn't decompress attribute list "
+ "runlist. Succeed anyway.\n");
+ return 0;
+ }
+ if (ntfs_cluster_free_from_rl(base_ni->vol, al_rl)) {
+ ntfs_log_trace("Leaking clusters! Run chkdsk. "
+ "Couldn't free clusters from "
+ "attribute list runlist.\n");
+ }
+ free(al_rl);
+ }
+ /* Remove attribute record itself. */
+ if (ntfs_attr_record_rm(ctx)) {
+ /*
+ * FIXME: Should we succeed here? BTW, chkdsk doesn't
+ * complain if it find MFT record with attribute list,
+ * but without extents.
+ */
+ ntfs_log_trace("Couldn't remove attribute list. Succeed "
+ "anyway.\n");
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ntfs_attr_add - add attribute to inode
+ * @ni: opened ntfs inode to which add attribute
+ * @type: type of the new attribute
+ * @name: name in unicode of the new attribute
+ * @name_len: name length in unicode characters of the new attribute
+ * @val: value of new attribute
+ * @size: size of the new attribute / length of @val (if specified)
+ *
+ * @val should always be specified for always resident attributes (eg. FILE_NAME
+ * attribute), for attributes that can become non-resident @val can be NULL
+ * (eg. DATA attribute). @size can be specified even if @val is NULL, in this
+ * case data size will be equal to @size and initialized size will be equal
+ * to 0.
+ *
+ * If inode haven't got enough space to add attribute, add attribute to one of
+ * it extents, if no extents present or no one of them have enough space, than
+ * allocate new extent and add attribute to it.
+ *
+ * If on one of this steps attribute list is needed but not present, than it is
+ * added transparently to caller. So, this function should not be called with
+ * @type == AT_ATTRIBUTE_LIST, if you really need to add attribute list call
+ * ntfs_inode_add_attrlist instead.
+ *
+ * On success return 0. On error return -1 with errno set to the error code.
+ */
+int ntfs_attr_add(ntfs_inode *ni, ATTR_TYPES type,
+ ntfschar *name, u8 name_len, u8 *val, s64 size)
+{
+ u32 attr_rec_size;
+ int err, i, offset;
+ BOOL is_resident;
+ BOOL can_be_non_resident = FALSE;
+ ntfs_inode *attr_ni;
+ ntfs_attr *na;
+ ATTR_FLAGS data_flags;
+
+ if (!ni || size < 0 || type == AT_ATTRIBUTE_LIST) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: ni=%p size=%lld", __FUNCTION__, ni,
+ (long long)size);
+ return -1;
+ }
+
+ ntfs_log_trace("Entering for inode %lld, attr %x, size %lld.\n",
+ (long long)ni->mft_no, type, (long long)size);
+
+ if (ni->nr_extents == -1)
+ ni = ni->base_ni;
+
+ /* Check the attribute type and the size. */
+ if (ntfs_attr_size_bounds_check(ni->vol, type, size)) {
+ if (errno == ENOENT)
+ errno = EIO;
+ return -1;
+ }
+
+ /* Sanity checks for always resident attributes. */
+ if (ntfs_attr_can_be_non_resident(ni->vol, type, name, name_len)) {
+ if (errno != EPERM) {
+ err = errno;
+ ntfs_log_perror("ntfs_attr_can_be_non_resident failed");
+ goto err_out;
+ }
+ /* @val is mandatory. */
+ if (!val) {
+ errno = EINVAL;
+ ntfs_log_perror("val is mandatory for always resident "
+ "attributes");
+ return -1;
+ }
+ if (size > ni->vol->mft_record_size) {
+ errno = ERANGE;
+ ntfs_log_perror("Attribute is too big");
+ return -1;
+ }
+ } else
+ can_be_non_resident = TRUE;
+
+ /*
+ * Determine resident or not will be new attribute. We add 8 to size in
+ * non resident case for mapping pairs.
+ */
+ if (!ntfs_attr_can_be_resident(ni->vol, type)) {
+ is_resident = TRUE;
+ } else {
+ if (errno != EPERM) {
+ err = errno;
+ ntfs_log_perror("ntfs_attr_can_be_resident failed");
+ goto err_out;
+ }
+ is_resident = FALSE;
+ }
+ /* Calculate attribute record size. */
+ if (is_resident)
+ attr_rec_size = offsetof(ATTR_RECORD, resident_end) +
+ ((name_len * sizeof(ntfschar) + 7) & ~7) +
+ ((size + 7) & ~7);
+ else
+ attr_rec_size = offsetof(ATTR_RECORD, non_resident_end) +
+ ((name_len * sizeof(ntfschar) + 7) & ~7) + 8;
+
+ /*
+ * If we have enough free space for the new attribute in the base MFT
+ * record, then add attribute to it.
+ */
+ if (le32_to_cpu(ni->mrec->bytes_allocated) -
+ le32_to_cpu(ni->mrec->bytes_in_use) >= attr_rec_size) {
+ attr_ni = ni;
+ goto add_attr_record;
+ }
+
+ /* Try to add to extent inodes. */
+ if (ntfs_inode_attach_all_extents(ni)) {
+ err = errno;
+ ntfs_log_perror("Failed to attach all extents to inode");
+ goto err_out;
+ }
+ for (i = 0; i < ni->nr_extents; i++) {
+ attr_ni = ni->extent_nis[i];
+ if (le32_to_cpu(attr_ni->mrec->bytes_allocated) -
+ le32_to_cpu(attr_ni->mrec->bytes_in_use) >=
+ attr_rec_size)
+ goto add_attr_record;
+ }
+
+ /* There is no extent that contain enough space for new attribute. */
+ if (!NInoAttrList(ni)) {
+ /* Add attribute list not present, add it and retry. */
+ if (ntfs_inode_add_attrlist(ni)) {
+ err = errno;
+ ntfs_log_perror("Failed to add attribute list");
+ goto err_out;
+ }
+ return ntfs_attr_add(ni, type, name, name_len, val, size);
+ }
+ /* Allocate new extent. */
+ attr_ni = ntfs_mft_record_alloc(ni->vol, ni);
+ if (!attr_ni) {
+ err = errno;
+ ntfs_log_perror("Failed to allocate extent record");
+ goto err_out;
+ }
+
+add_attr_record:
+ if ((ni->flags & FILE_ATTR_COMPRESSED)
+ && (ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)
+ && ((type == AT_DATA)
+ || ((type == AT_INDEX_ROOT) && (name == NTFS_INDEX_I30))))
+ data_flags = ATTR_IS_COMPRESSED;
+ else
+ data_flags = const_cpu_to_le16(0);
+ if (is_resident) {
+ /* Add resident attribute. */
+ offset = ntfs_resident_attr_record_add(attr_ni, type, name,
+ name_len, val, size, data_flags);
+ if (offset < 0) {
+ if (errno == ENOSPC && can_be_non_resident)
+ goto add_non_resident;
+ err = errno;
+ ntfs_log_perror("Failed to add resident attribute");
+ goto free_err_out;
+ }
+ return 0;
+ }
+
+add_non_resident:
+ /* Add non resident attribute. */
+ offset = ntfs_non_resident_attr_record_add(attr_ni, type, name,
+ name_len, 0, 8, data_flags);
+ if (offset < 0) {
+ err = errno;
+ ntfs_log_perror("Failed to add non resident attribute");
+ goto free_err_out;
+ }
+
+ /* If @size == 0, we are done. */
+ if (!size)
+ return 0;
+
+ /* Open new attribute and resize it. */
+ na = ntfs_attr_open(ni, type, name, name_len);
+ if (!na) {
+ err = errno;
+ ntfs_log_perror("Failed to open just added attribute");
+ goto rm_attr_err_out;
+ }
+ /* Resize and set attribute value. */
+ if (ntfs_attr_truncate(na, size) ||
+ (val && (ntfs_attr_pwrite(na, 0, size, val) != size))) {
+ err = errno;
+ ntfs_log_perror("Failed to initialize just added attribute");
+ if (ntfs_attr_rm(na))
+ ntfs_log_perror("Failed to remove just added attribute");
+ ntfs_attr_close(na);
+ goto err_out;
+ }
+ ntfs_attr_close(na);
+ return 0;
+
+rm_attr_err_out:
+ /* Remove just added attribute. */
+ if (ntfs_attr_record_resize(attr_ni->mrec,
+ (ATTR_RECORD*)((u8*)attr_ni->mrec + offset), 0))
+ ntfs_log_perror("Failed to remove just added attribute #2");
+free_err_out:
+ /* Free MFT record, if it doesn't contain attributes. */
+ if (le32_to_cpu(attr_ni->mrec->bytes_in_use) -
+ le16_to_cpu(attr_ni->mrec->attrs_offset) == 8)
+ if (ntfs_mft_record_free(attr_ni->vol, attr_ni))
+ ntfs_log_perror("Failed to free MFT record");
+err_out:
+ errno = err;
+ return -1;
+}
+
+/*
+ * Change an attribute flag
+ */
+
+int ntfs_attr_set_flags(ntfs_inode *ni, ATTR_TYPES type,
+ ntfschar *name, u8 name_len, ATTR_FLAGS flags, ATTR_FLAGS mask)
+{
+ ntfs_attr_search_ctx *ctx;
+ int res;
+
+ res = -1;
+ /* Search for designated attribute */
+ ctx = ntfs_attr_get_search_ctx(ni, NULL);
+ if (ctx) {
+ if (!ntfs_attr_lookup(type, name, name_len,
+ CASE_SENSITIVE, 0, NULL, 0, ctx)) {
+ /* do the requested change (all small endian le16) */
+ ctx->attr->flags = (ctx->attr->flags & ~mask)
+ | (flags & mask);
+ NInoSetDirty(ni);
+ res = 0;
+ }
+ ntfs_attr_put_search_ctx(ctx);
+ }
+ return (res);
+}
+
+
+/**
+ * ntfs_attr_rm - remove attribute from ntfs inode
+ * @na: opened ntfs attribute to delete
+ *
+ * Remove attribute and all it's extents from ntfs inode. If attribute was non
+ * resident also free all clusters allocated by attribute.
+ *
+ * Return 0 on success or -1 on error with errno set to the error code.
+ */
+int ntfs_attr_rm(ntfs_attr *na)
+{
+ ntfs_attr_search_ctx *ctx;
+ int ret = 0;
+
+ if (!na) {
+ ntfs_log_trace("Invalid arguments passed.\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n",
+ (long long) na->ni->mft_no, na->type);
+
+ /* Free cluster allocation. */
+ if (NAttrNonResident(na)) {
+ if (ntfs_attr_map_whole_runlist(na))
+ return -1;
+ if (ntfs_cluster_free(na->ni->vol, na, 0, -1) < 0) {
+ ntfs_log_trace("Failed to free cluster allocation. Leaving "
+ "inconstant metadata.\n");
+ ret = -1;
+ }
+ }
+
+ /* Search for attribute extents and remove them all. */
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ return -1;
+ while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
+ CASE_SENSITIVE, 0, NULL, 0, ctx)) {
+ if (ntfs_attr_record_rm(ctx)) {
+ ntfs_log_trace("Failed to remove attribute extent. Leaving "
+ "inconstant metadata.\n");
+ ret = -1;
+ }
+ ntfs_attr_reinit_search_ctx(ctx);
+ }
+ ntfs_attr_put_search_ctx(ctx);
+ if (errno != ENOENT) {
+ ntfs_log_trace("Attribute lookup failed. Probably leaving inconstant "
+ "metadata.\n");
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/**
+ * ntfs_attr_record_resize - resize an attribute record
+ * @m: mft record containing attribute record
+ * @a: attribute record to resize
+ * @new_size: new size in bytes to which to resize the attribute record @a
+ *
+ * Resize the attribute record @a, i.e. the resident part of the attribute, in
+ * the mft record @m to @new_size bytes.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code.
+ * The following error codes are defined:
+ * ENOSPC - Not enough space in the mft record @m to perform the resize.
+ * Note that on error no modifications have been performed whatsoever.
+ *
+ * Warning: If you make a record smaller without having copied all the data you
+ * are interested in the data may be overwritten!
+ */
+int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size)
+{
+ u32 old_size, alloc_size, attr_size;
+
+ old_size = le32_to_cpu(m->bytes_in_use);
+ alloc_size = le32_to_cpu(m->bytes_allocated);
+ attr_size = le32_to_cpu(a->length);
+
+ ntfs_log_trace("Sizes: old=%u alloc=%u attr=%u new=%u\n",
+ (unsigned)old_size, (unsigned)alloc_size,
+ (unsigned)attr_size, (unsigned)new_size);
+
+ /* Align to 8 bytes, just in case the caller hasn't. */
+ new_size = (new_size + 7) & ~7;
+
+ /* If the actual attribute length has changed, move things around. */
+ if (new_size != attr_size) {
+
+ u32 new_muse = old_size - attr_size + new_size;
+
+ /* Not enough space in this mft record. */
+ if (new_muse > alloc_size) {
+ errno = ENOSPC;
+ ntfs_log_trace("Not enough space in the MFT record "
+ "(%u > %u)\n", new_muse, alloc_size);
+ return -1;
+ }
+
+ if (a->type == AT_INDEX_ROOT && new_size > attr_size &&
+ new_muse + 120 > alloc_size && old_size + 120 <= alloc_size) {
+ errno = ENOSPC;
+ ntfs_log_trace("Too big INDEX_ROOT (%u > %u)\n",
+ new_muse, alloc_size);
+ return STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT;
+ }
+
+ /* Move attributes following @a to their new location. */
+ memmove((u8 *)a + new_size, (u8 *)a + attr_size,
+ old_size - ((u8 *)a - (u8 *)m) - attr_size);
+
+ /* Adjust @m to reflect the change in used space. */
+ m->bytes_in_use = cpu_to_le32(new_muse);
+
+ /* Adjust @a to reflect the new size. */
+ if (new_size >= offsetof(ATTR_REC, length) + sizeof(a->length))
+ a->length = cpu_to_le32(new_size);
+ }
+ return 0;
+}
+
+/**
+ * ntfs_resident_attr_value_resize - resize the value of a resident attribute
+ * @m: mft record containing attribute record
+ * @a: attribute record whose value to resize
+ * @new_size: new size in bytes to which to resize the attribute value of @a
+ *
+ * Resize the value of the attribute @a in the mft record @m to @new_size bytes.
+ * If the value is made bigger, the newly "allocated" space is cleared.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code.
+ * The following error codes are defined:
+ * ENOSPC - Not enough space in the mft record @m to perform the resize.
+ * Note that on error no modifications have been performed whatsoever.
+ */
+int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a,
+ const u32 new_size)
+{
+ int ret;
+
+ ntfs_log_trace("Entering for new size %u.\n", (unsigned)new_size);
+
+ /* Resize the resident part of the attribute record. */
+ if ((ret = ntfs_attr_record_resize(m, a, (le16_to_cpu(a->value_offset) +
+ new_size + 7) & ~7)) < 0)
+ return ret;
+ /*
+ * If we made the attribute value bigger, clear the area between the
+ * old size and @new_size.
+ */
+ if (new_size > le32_to_cpu(a->value_length))
+ memset((u8*)a + le16_to_cpu(a->value_offset) +
+ le32_to_cpu(a->value_length), 0, new_size -
+ le32_to_cpu(a->value_length));
+ /* Finally update the length of the attribute value. */
+ a->value_length = cpu_to_le32(new_size);
+ return 0;
+}
+
+/**
+ * ntfs_attr_record_move_to - move attribute record to target inode
+ * @ctx: attribute search context describing the attribute record
+ * @ni: opened ntfs inode to which move attribute record
+ *
+ * If this function succeed, user should reinit search context if he/she wants
+ * use it anymore.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code.
+ */
+int ntfs_attr_record_move_to(ntfs_attr_search_ctx *ctx, ntfs_inode *ni)
+{
+ ntfs_attr_search_ctx *nctx;
+ ATTR_RECORD *a;
+ int err;
+
+ if (!ctx || !ctx->attr || !ctx->ntfs_ino || !ni) {
+ ntfs_log_trace("Invalid arguments passed.\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ ntfs_log_trace("Entering for ctx->attr->type 0x%x, ctx->ntfs_ino->mft_no "
+ "0x%llx, ni->mft_no 0x%llx.\n",
+ (unsigned) le32_to_cpu(ctx->attr->type),
+ (long long) ctx->ntfs_ino->mft_no,
+ (long long) ni->mft_no);
+
+ if (ctx->ntfs_ino == ni)
+ return 0;
+
+ if (!ctx->al_entry) {
+ ntfs_log_trace("Inode should contain attribute list to use this "
+ "function.\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Find place in MFT record where attribute will be moved. */
+ a = ctx->attr;
+ nctx = ntfs_attr_get_search_ctx(ni, NULL);
+ if (!nctx)
+ return -1;
+
+ /*
+ * Use ntfs_attr_find instead of ntfs_attr_lookup to find place for
+ * attribute in @ni->mrec, not any extent inode in case if @ni is base
+ * file record.
+ */
+ if (!ntfs_attr_find(a->type, (ntfschar*)((u8*)a + le16_to_cpu(
+ a->name_offset)), a->name_length, CASE_SENSITIVE, NULL,
+ 0, nctx)) {
+ ntfs_log_trace("Attribute of such type, with same name already "
+ "present in this MFT record.\n");
+ err = EEXIST;
+ goto put_err_out;
+ }
+ if (errno != ENOENT) {
+ err = errno;
+ ntfs_log_debug("Attribute lookup failed.\n");
+ goto put_err_out;
+ }
+
+ /* Make space and move attribute. */
+ if (ntfs_make_room_for_attr(ni->mrec, (u8*) nctx->attr,
+ le32_to_cpu(a->length))) {
+ err = errno;
+ ntfs_log_trace("Couldn't make space for attribute.\n");
+ goto put_err_out;
+ }
+ memcpy(nctx->attr, a, le32_to_cpu(a->length));
+ nctx->attr->instance = nctx->mrec->next_attr_instance;
+ nctx->mrec->next_attr_instance = cpu_to_le16(
+ (le16_to_cpu(nctx->mrec->next_attr_instance) + 1) & 0xffff);
+ ntfs_attr_record_resize(ctx->mrec, a, 0);
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ ntfs_inode_mark_dirty(ni);
+
+ /* Update attribute list. */
+ ctx->al_entry->mft_reference =
+ MK_LE_MREF(ni->mft_no, le16_to_cpu(ni->mrec->sequence_number));
+ ctx->al_entry->instance = nctx->attr->instance;
+ ntfs_attrlist_mark_dirty(ni);
+
+ ntfs_attr_put_search_ctx(nctx);
+ return 0;
+put_err_out:
+ ntfs_attr_put_search_ctx(nctx);
+ errno = err;
+ return -1;
+}
+
+/**
+ * ntfs_attr_record_move_away - move away attribute record from it's mft record
+ * @ctx: attribute search context describing the attribute record
+ * @extra: minimum amount of free space in the new holder of record
+ *
+ * New attribute record holder must have free @extra bytes after moving
+ * attribute record to it.
+ *
+ * If this function succeed, user should reinit search context if he/she wants
+ * use it anymore.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code.
+ */
+int ntfs_attr_record_move_away(ntfs_attr_search_ctx *ctx, int extra)
+{
+ ntfs_inode *base_ni, *ni;
+ MFT_RECORD *m;
+ int i;
+
+ if (!ctx || !ctx->attr || !ctx->ntfs_ino || extra < 0) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: ctx=%p ctx->attr=%p extra=%d", __FUNCTION__,
+ ctx, ctx ? ctx->attr : NULL, extra);
+ return -1;
+ }
+
+ ntfs_log_trace("Entering for attr 0x%x, inode %llu\n",
+ (unsigned) le32_to_cpu(ctx->attr->type),
+ (unsigned long long)ctx->ntfs_ino->mft_no);
+
+ if (ctx->ntfs_ino->nr_extents == -1)
+ base_ni = ctx->base_ntfs_ino;
+ else
+ base_ni = ctx->ntfs_ino;
+
+ if (!NInoAttrList(base_ni)) {
+ errno = EINVAL;
+ ntfs_log_perror("Inode %llu has no attrlist",
+ (unsigned long long)base_ni->mft_no);
+ return -1;
+ }
+
+ if (ntfs_inode_attach_all_extents(ctx->ntfs_ino)) {
+ ntfs_log_perror("Couldn't attach extents, inode=%llu",
+ (unsigned long long)base_ni->mft_no);
+ return -1;
+ }
+
+ /* Walk through all extents and try to move attribute to them. */
+ for (i = 0; i < base_ni->nr_extents; i++) {
+ ni = base_ni->extent_nis[i];
+ m = ni->mrec;
+
+ if (ctx->ntfs_ino->mft_no == ni->mft_no)
+ continue;
+
+ if (le32_to_cpu(m->bytes_allocated) -
+ le32_to_cpu(m->bytes_in_use) <
+ le32_to_cpu(ctx->attr->length) + extra)
+ continue;
+
+ /*
+ * ntfs_attr_record_move_to can fail if extent with other lowest
+ * VCN already present in inode we trying move record to. So,
+ * do not return error.
+ */
+ if (!ntfs_attr_record_move_to(ctx, ni))
+ return 0;
+ }
+
+ /*
+ * Failed to move attribute to one of the current extents, so allocate
+ * new extent and move attribute to it.
+ */
+ ni = ntfs_mft_record_alloc(base_ni->vol, base_ni);
+ if (!ni) {
+ ntfs_log_perror("Couldn't allocate MFT record");
+ return -1;
+ }
+ if (ntfs_attr_record_move_to(ctx, ni)) {
+ ntfs_log_perror("Couldn't move attribute to MFT record");
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * ntfs_attr_make_non_resident - convert a resident to a non-resident attribute
+ * @na: open ntfs attribute to make non-resident
+ * @ctx: ntfs search context describing the attribute
+ *
+ * Convert a resident ntfs attribute to a non-resident one.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code. The
+ * following error codes are defined:
+ * EPERM - The attribute is not allowed to be non-resident.
+ * TODO: others...
+ *
+ * NOTE to self: No changes in the attribute list are required to move from
+ * a resident to a non-resident attribute.
+ *
+ * Warning: We do not set the inode dirty and we do not write out anything!
+ * We expect the caller to do this as this is a fairly low level
+ * function and it is likely there will be further changes made.
+ */
+int ntfs_attr_make_non_resident(ntfs_attr *na,
+ ntfs_attr_search_ctx *ctx)
+{
+ s64 new_allocated_size, bw;
+ ntfs_volume *vol = na->ni->vol;
+ ATTR_REC *a = ctx->attr;
+ runlist *rl;
+ int mp_size, mp_ofs, name_ofs, arec_size, err;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
+ long)na->ni->mft_no, na->type);
+
+ /* Some preliminary sanity checking. */
+ if (NAttrNonResident(na)) {
+ ntfs_log_trace("Eeek! Trying to make non-resident attribute "
+ "non-resident. Aborting...\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Check that the attribute is allowed to be non-resident. */
+ if (ntfs_attr_can_be_non_resident(vol, na->type, na->name, na->name_len))
+ return -1;
+
+ new_allocated_size = (le32_to_cpu(a->value_length) + vol->cluster_size
+ - 1) & ~(vol->cluster_size - 1);
+
+ if (new_allocated_size > 0) {
+ /* Start by allocating clusters to hold the attribute value. */
+ rl = ntfs_cluster_alloc(vol, 0, new_allocated_size >>
+ vol->cluster_size_bits, -1, DATA_ZONE);
+ if (!rl)
+ return -1;
+ } else
+ rl = NULL;
+ /*
+ * Setup the in-memory attribute structure to be non-resident so that
+ * we can use ntfs_attr_pwrite().
+ */
+ NAttrSetNonResident(na);
+ na->rl = rl;
+ na->allocated_size = new_allocated_size;
+ na->data_size = na->initialized_size = le32_to_cpu(a->value_length);
+ /*
+ * FIXME: For now just clear all of these as we don't support them when
+ * writing.
+ */
+ NAttrClearSparse(na);
+ NAttrClearEncrypted(na);
+ if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) {
+ /* set compression writing parameters */
+ na->compression_block_size
+ = 1 << (STANDARD_COMPRESSION_UNIT + vol->cluster_size_bits);
+ na->compression_block_clusters = 1 << STANDARD_COMPRESSION_UNIT;
+ }
+
+ if (rl) {
+ /* Now copy the attribute value to the allocated cluster(s). */
+ bw = ntfs_attr_pwrite(na, 0, le32_to_cpu(a->value_length),
+ (u8*)a + le16_to_cpu(a->value_offset));
+ if (bw != le32_to_cpu(a->value_length)) {
+ err = errno;
+ ntfs_log_debug("Eeek! Failed to write out attribute value "
+ "(bw = %lli, errno = %i). "
+ "Aborting...\n", (long long)bw, err);
+ if (bw >= 0)
+ err = EIO;
+ goto cluster_free_err_out;
+ }
+ }
+ /* Determine the size of the mapping pairs array. */
+ mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0, INT_MAX);
+ if (mp_size < 0) {
+ err = errno;
+ ntfs_log_debug("Eeek! Failed to get size for mapping pairs array. "
+ "Aborting...\n");
+ goto cluster_free_err_out;
+ }
+ /* Calculate new offsets for the name and the mapping pairs array. */
+ if (na->ni->flags & FILE_ATTR_COMPRESSED)
+ name_ofs = (sizeof(ATTR_REC) + 7) & ~7;
+ else
+ name_ofs = (sizeof(ATTR_REC) - sizeof(a->compressed_size) + 7) & ~7;
+ mp_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7;
+ /*
+ * Determine the size of the resident part of the non-resident
+ * attribute record. (Not compressed thus no compressed_size element
+ * present.)
+ */
+ arec_size = (mp_ofs + mp_size + 7) & ~7;
+
+ /* Resize the resident part of the attribute record. */
+ if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) {
+ err = errno;
+ goto cluster_free_err_out;
+ }
+
+ /*
+ * Convert the resident part of the attribute record to describe a
+ * non-resident attribute.
+ */
+ a->non_resident = 1;
+
+ /* Move the attribute name if it exists and update the offset. */
+ if (a->name_length)
+ memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset),
+ a->name_length * sizeof(ntfschar));
+ a->name_offset = cpu_to_le16(name_ofs);
+
+ /* Setup the fields specific to non-resident attributes. */
+ a->lowest_vcn = cpu_to_sle64(0);
+ a->highest_vcn = cpu_to_sle64((new_allocated_size - 1) >>
+ vol->cluster_size_bits);
+
+ a->mapping_pairs_offset = cpu_to_le16(mp_ofs);
+
+ /*
+ * Update the flags to match the in-memory ones.
+ * However cannot change the compression state if we had
+ * a fuse_file_info open with a mark for release.
+ * The decisions about compression can only be made when
+ * creating/recreating the stream, not when making non resident.
+ */
+ a->flags &= ~(ATTR_IS_SPARSE | ATTR_IS_ENCRYPTED);
+ if ((a->flags & ATTR_COMPRESSION_MASK) == ATTR_IS_COMPRESSED) {
+ /* support only ATTR_IS_COMPRESSED compression mode */
+ a->compression_unit = STANDARD_COMPRESSION_UNIT;
+ a->compressed_size = const_cpu_to_le64(0);
+ } else {
+ a->compression_unit = 0;
+ a->flags &= ~ATTR_COMPRESSION_MASK;
+ na->data_flags = a->flags;
+ }
+
+ memset(&a->reserved1, 0, sizeof(a->reserved1));
+
+ a->allocated_size = cpu_to_sle64(new_allocated_size);
+ a->data_size = a->initialized_size = cpu_to_sle64(na->data_size);
+
+ /* Generate the mapping pairs array in the attribute record. */
+ if (ntfs_mapping_pairs_build(vol, (u8*)a + mp_ofs, arec_size - mp_ofs,
+ rl, 0, NULL) < 0) {
+ // FIXME: Eeek! We need rollback! (AIA)
+ ntfs_log_trace("Eeek! Failed to build mapping pairs. Leaving "
+ "corrupt attribute record on disk. In memory "
+ "runlist is still intact! Error code is %i. "
+ "FIXME: Need to rollback instead!\n", errno);
+ return -1;
+ }
+
+ /* Done! */
+ return 0;
+
+cluster_free_err_out:
+ if (rl && ntfs_cluster_free(vol, na, 0, -1) < 0)
+ ntfs_log_trace("Eeek! Failed to release allocated clusters in error "
+ "code path. Leaving inconsistent metadata...\n");
+ NAttrClearNonResident(na);
+ na->allocated_size = na->data_size;
+ na->rl = NULL;
+ free(rl);
+ errno = err;
+ return -1;
+}
+
+
+static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize);
+
+/**
+ * ntfs_resident_attr_resize - resize a resident, open ntfs attribute
+ * @na: resident ntfs attribute to resize
+ * @newsize: new size (in bytes) to which to resize the attribute
+ *
+ * Change the size of a resident, open ntfs attribute @na to @newsize bytes.
+ *
+ * On success return 0
+ * On error return values are:
+ * STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT
+ * STATUS_ERROR - otherwise
+ * The following error codes are defined:
+ * ENOMEM - Not enough memory to complete operation.
+ * ERANGE - @newsize is not valid for the attribute type of @na.
+ * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST.
+ */
+static int ntfs_resident_attr_resize_i(ntfs_attr *na, const s64 newsize)
+{
+ ntfs_attr_search_ctx *ctx;
+ ntfs_volume *vol;
+ ntfs_inode *ni;
+ int err, ret = STATUS_ERROR;
+
+ ntfs_log_trace("Inode 0x%llx attr 0x%x new size %lld\n",
+ (unsigned long long)na->ni->mft_no, na->type,
+ (long long)newsize);
+
+ /* Get the attribute record that needs modification. */
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ return -1;
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, 0, 0, NULL, 0,
+ ctx)) {
+ err = errno;
+ ntfs_log_perror("ntfs_attr_lookup failed");
+ goto put_err_out;
+ }
+ vol = na->ni->vol;
+ /*
+ * Check the attribute type and the corresponding minimum and maximum
+ * sizes against @newsize and fail if @newsize is out of bounds.
+ */
+ if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) {
+ err = errno;
+ if (err == ENOENT)
+ err = EIO;
+ ntfs_log_perror("%s: bounds check failed", __FUNCTION__);
+ goto put_err_out;
+ }
+ /*
+ * If @newsize is bigger than the mft record we need to make the
+ * attribute non-resident if the attribute type supports it. If it is
+ * smaller we can go ahead and attempt the resize.
+ */
+ if (newsize < vol->mft_record_size) {
+ /* Perform the resize of the attribute record. */
+ if (!(ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr,
+ newsize))) {
+ /* Update attribute size everywhere. */
+ na->data_size = na->initialized_size = newsize;
+ na->allocated_size = (newsize + 7) & ~7;
+ if ((na->data_flags & ATTR_COMPRESSION_MASK)
+ || NAttrSparse(na))
+ na->compressed_size = na->allocated_size;
+ if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY
+ ? na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30
+ : na->type == AT_DATA && na->name == AT_UNNAMED) {
+ na->ni->data_size = na->data_size;
+ na->ni->allocated_size = na->allocated_size;
+ set_nino_flag(na->ni,KnownSize);
+ if (na->type == AT_DATA)
+ NInoFileNameSetDirty(na->ni);
+ }
+ goto resize_done;
+ }
+ /* Prefer AT_INDEX_ALLOCATION instead of AT_ATTRIBUTE_LIST */
+ if (ret == STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT) {
+ err = errno;
+ goto put_err_out;
+ }
+ }
+ /* There is not enough space in the mft record to perform the resize. */
+
+ /* Make the attribute non-resident if possible. */
+ if (!ntfs_attr_make_non_resident(na, ctx)) {
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ ntfs_attr_put_search_ctx(ctx);
+ /* Resize non-resident attribute */
+ return ntfs_attr_truncate(na, newsize);
+ } else if (errno != ENOSPC && errno != EPERM) {
+ err = errno;
+ ntfs_log_perror("Failed to make attribute non-resident");
+ goto put_err_out;
+ }
+
+ /* Try to make other attributes non-resident and retry each time. */
+ ntfs_attr_init_search_ctx(ctx, NULL, na->ni->mrec);
+ while (!ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx)) {
+ ntfs_attr *tna;
+ ATTR_RECORD *a;
+
+ a = ctx->attr;
+ if (a->non_resident)
+ continue;
+
+ /*
+ * Check out whether convert is reasonable. Assume that mapping
+ * pairs will take 8 bytes.
+ */
+ if (le32_to_cpu(a->length) <= offsetof(ATTR_RECORD,
+ compressed_size) + ((a->name_length *
+ sizeof(ntfschar) + 7) & ~7) + 8)
+ continue;
+
+ tna = ntfs_attr_open(na->ni, a->type, (ntfschar*)((u8*)a +
+ le16_to_cpu(a->name_offset)), a->name_length);
+ if (!tna) {
+ err = errno;
+ ntfs_log_perror("Couldn't open attribute");
+ goto put_err_out;
+ }
+ if (ntfs_attr_make_non_resident(tna, ctx)) {
+ ntfs_attr_close(tna);
+ continue;
+ }
+ ntfs_inode_mark_dirty(tna->ni);
+ ntfs_attr_close(tna);
+ ntfs_attr_put_search_ctx(ctx);
+ return ntfs_resident_attr_resize(na, newsize);
+ }
+ /* Check whether error occurred. */
+ if (errno != ENOENT) {
+ err = errno;
+ ntfs_log_perror("%s: Attribute lookup failed 1", __FUNCTION__);
+ goto put_err_out;
+ }
+
+ /*
+ * The standard information and attribute list attributes can't be
+ * moved out from the base MFT record, so try to move out others.
+ */
+ if (na->type==AT_STANDARD_INFORMATION || na->type==AT_ATTRIBUTE_LIST) {
+ ntfs_attr_put_search_ctx(ctx);
+ if (ntfs_inode_free_space(na->ni, offsetof(ATTR_RECORD,
+ non_resident_end) + 8)) {
+ ntfs_log_perror("Could not free space in MFT record");
+ return -1;
+ }
+ return ntfs_resident_attr_resize(na, newsize);
+ }
+
+ /*
+ * Move the attribute to a new mft record, creating an attribute list
+ * attribute or modifying it if it is already present.
+ */
+
+ /* Point search context back to attribute which we need resize. */
+ ntfs_attr_init_search_ctx(ctx, na->ni, NULL);
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
+ 0, NULL, 0, ctx)) {
+ ntfs_log_perror("%s: Attribute lookup failed 2", __FUNCTION__);
+ err = errno;
+ goto put_err_out;
+ }
+
+ /*
+ * Check whether attribute is already single in this MFT record.
+ * 8 added for the attribute terminator.
+ */
+ if (le32_to_cpu(ctx->mrec->bytes_in_use) ==
+ le16_to_cpu(ctx->mrec->attrs_offset) +
+ le32_to_cpu(ctx->attr->length) + 8) {
+ err = ENOSPC;
+ ntfs_log_trace("MFT record is filled with one attribute\n");
+ ret = STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT;
+ goto put_err_out;
+ }
+
+ /* Add attribute list if not present. */
+ if (na->ni->nr_extents == -1)
+ ni = na->ni->base_ni;
+ else
+ ni = na->ni;
+ if (!NInoAttrList(ni)) {
+ ntfs_attr_put_search_ctx(ctx);
+ if (ntfs_inode_add_attrlist(ni))
+ return -1;
+ return ntfs_resident_attr_resize(na, newsize);
+ }
+ /* Allocate new mft record. */
+ ni = ntfs_mft_record_alloc(vol, ni);
+ if (!ni) {
+ err = errno;
+ ntfs_log_perror("Couldn't allocate new MFT record");
+ goto put_err_out;
+ }
+ /* Move attribute to it. */
+ if (ntfs_attr_record_move_to(ctx, ni)) {
+ err = errno;
+ ntfs_log_perror("Couldn't move attribute to new MFT record");
+ goto put_err_out;
+ }
+ /* Update ntfs attribute. */
+ if (na->ni->nr_extents == -1)
+ na->ni = ni;
+
+ ntfs_attr_put_search_ctx(ctx);
+ /* Try to perform resize once again. */
+ return ntfs_resident_attr_resize(na, newsize);
+
+resize_done:
+ /*
+ * Set the inode (and its base inode if it exists) dirty so it is
+ * written out later.
+ */
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ ntfs_attr_put_search_ctx(ctx);
+ return 0;
+put_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+ errno = err;
+ return ret;
+}
+
+static int ntfs_resident_attr_resize(ntfs_attr *na, const s64 newsize)
+{
+ int ret;
+
+ ntfs_log_enter("Entering\n");
+ ret = ntfs_resident_attr_resize_i(na, newsize);
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+/**
+ * ntfs_attr_make_resident - convert a non-resident to a resident attribute
+ * @na: open ntfs attribute to make resident
+ * @ctx: ntfs search context describing the attribute
+ *
+ * Convert a non-resident ntfs attribute to a resident one.
+ *
+ * Return 0 on success and -1 on error with errno set to the error code. The
+ * following error codes are defined:
+ * EINVAL - Invalid arguments passed.
+ * EPERM - The attribute is not allowed to be resident.
+ * EIO - I/O error, damaged inode or bug.
+ * ENOSPC - There is no enough space to perform conversion.
+ * EOPNOTSUPP - Requested conversion is not supported yet.
+ *
+ * Warning: We do not set the inode dirty and we do not write out anything!
+ * We expect the caller to do this as this is a fairly low level
+ * function and it is likely there will be further changes made.
+ */
+static int ntfs_attr_make_resident(ntfs_attr *na, ntfs_attr_search_ctx *ctx)
+{
+ ntfs_volume *vol = na->ni->vol;
+ ATTR_REC *a = ctx->attr;
+ int name_ofs, val_ofs, err = EIO;
+ s64 arec_size, bytes_read;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x.\n", (unsigned long
+ long)na->ni->mft_no, na->type);
+
+ /* Should be called for the first extent of the attribute. */
+ if (sle64_to_cpu(a->lowest_vcn)) {
+ ntfs_log_trace("Eeek! Should be called for the first extent of the "
+ "attribute. Aborting...\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Some preliminary sanity checking. */
+ if (!NAttrNonResident(na)) {
+ ntfs_log_trace("Eeek! Trying to make resident attribute resident. "
+ "Aborting...\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Make sure this is not $MFT/$BITMAP or Windows will not boot! */
+ if (na->type == AT_BITMAP && na->ni->mft_no == FILE_MFT) {
+ errno = EPERM;
+ return -1;
+ }
+
+ /* Check that the attribute is allowed to be resident. */
+ if (ntfs_attr_can_be_resident(vol, na->type))
+ return -1;
+
+ if (na->data_flags & ATTR_IS_ENCRYPTED) {
+ ntfs_log_trace("Making encrypted streams resident is not "
+ "implemented yet.\n");
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+
+ /* Work out offsets into and size of the resident attribute. */
+ name_ofs = 24; /* = sizeof(resident_ATTR_REC); */
+ val_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7;
+ arec_size = (val_ofs + na->data_size + 7) & ~7;
+
+ /* Sanity check the size before we start modifying the attribute. */
+ if (le32_to_cpu(ctx->mrec->bytes_in_use) - le32_to_cpu(a->length) +
+ arec_size > le32_to_cpu(ctx->mrec->bytes_allocated)) {
+ errno = ENOSPC;
+ ntfs_log_trace("Not enough space to make attribute resident\n");
+ return -1;
+ }
+
+ /* Read and cache the whole runlist if not already done. */
+ if (ntfs_attr_map_whole_runlist(na))
+ return -1;
+
+ /* Move the attribute name if it exists and update the offset. */
+ if (a->name_length) {
+ memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset),
+ a->name_length * sizeof(ntfschar));
+ }
+ a->name_offset = cpu_to_le16(name_ofs);
+
+ /* Resize the resident part of the attribute record. */
+ if (ntfs_attr_record_resize(ctx->mrec, a, arec_size) < 0) {
+ /*
+ * Bug, because ntfs_attr_record_resize should not fail (we
+ * already checked that attribute fits MFT record).
+ */
+ ntfs_log_error("BUG! Failed to resize attribute record. "
+ "Please report to the %s. Aborting...\n",
+ NTFS_DEV_LIST);
+ errno = EIO;
+ return -1;
+ }
+
+ /* Convert the attribute record to describe a resident attribute. */
+ a->non_resident = 0;
+ a->flags = 0;
+ a->value_length = cpu_to_le32(na->data_size);
+ a->value_offset = cpu_to_le16(val_ofs);
+ /*
+ * If a data stream was wiped out, adjust the compression mode
+ * to current state of compression flag
+ */
+ if (!na->data_size
+ && (na->type == AT_DATA)
+ && (na->ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)
+ && (na->ni->flags & FILE_ATTR_COMPRESSED)) {
+ a->flags |= ATTR_IS_COMPRESSED;
+ na->data_flags = a->flags;
+ }
+ /*
+ * File names cannot be non-resident so we would never see this here
+ * but at least it serves as a reminder that there may be attributes
+ * for which we do need to set this flag. (AIA)
+ */
+ if (a->type == AT_FILE_NAME)
+ a->resident_flags = RESIDENT_ATTR_IS_INDEXED;
+ else
+ a->resident_flags = 0;
+ a->reservedR = 0;
+
+ /* Sanity fixup... Shouldn't really happen. (AIA) */
+ if (na->initialized_size > na->data_size)
+ na->initialized_size = na->data_size;
+
+ /* Copy data from run list to resident attribute value. */
+ bytes_read = ntfs_rl_pread(vol, na->rl, 0, na->initialized_size,
+ (u8*)a + val_ofs);
+ if (bytes_read != na->initialized_size) {
+ if (bytes_read < 0)
+ err = errno;
+ ntfs_log_trace("Eeek! Failed to read attribute data. Leaving "
+ "inconstant metadata. Run chkdsk. "
+ "Aborting...\n");
+ errno = err;
+ return -1;
+ }
+
+ /* Clear memory in gap between initialized_size and data_size. */
+ if (na->initialized_size < na->data_size)
+ memset((u8*)a + val_ofs + na->initialized_size, 0,
+ na->data_size - na->initialized_size);
+
+ /*
+ * Deallocate clusters from the runlist.
+ *
+ * NOTE: We can use ntfs_cluster_free() because we have already mapped
+ * the whole run list and thus it doesn't matter that the attribute
+ * record is in a transiently corrupted state at this moment in time.
+ */
+ if (ntfs_cluster_free(vol, na, 0, -1) < 0) {
+ err = errno;
+ ntfs_log_perror("Eeek! Failed to release allocated clusters");
+ ntfs_log_trace("Ignoring error and leaving behind wasted "
+ "clusters.\n");
+ }
+
+ /* Throw away the now unused runlist. */
+ free(na->rl);
+ na->rl = NULL;
+
+ /* Update in-memory struct ntfs_attr. */
+ NAttrClearNonResident(na);
+ NAttrClearSparse(na);
+ NAttrClearEncrypted(na);
+ na->initialized_size = na->data_size;
+ na->allocated_size = na->compressed_size = (na->data_size + 7) & ~7;
+ na->compression_block_size = 0;
+ na->compression_block_size_bits = na->compression_block_clusters = 0;
+ return 0;
+}
+
+/*
+ * If we are in the first extent, then set/clean sparse bit,
+ * update allocated and compressed size.
+ */
+static int ntfs_attr_update_meta(ATTR_RECORD *a, ntfs_attr *na, MFT_RECORD *m,
+ ntfs_attr_search_ctx *ctx)
+{
+ int sparse, ret = 0;
+
+ ntfs_log_trace("Entering for inode 0x%llx, attr 0x%x\n",
+ (unsigned long long)na->ni->mft_no, na->type);
+
+ if (a->lowest_vcn)
+ goto out;
+
+ a->allocated_size = cpu_to_sle64(na->allocated_size);
+
+ /* Update sparse bit. */
+ sparse = ntfs_rl_sparse(na->rl);
+ if (sparse == -1) {
+ errno = EIO;
+ goto error;
+ }
+
+ /* Attribute become sparse. */
+ if (sparse && !(a->flags & (ATTR_IS_SPARSE | ATTR_IS_COMPRESSED))) {
+ /*
+ * Move attribute to another mft record, if attribute is too
+ * small to add compressed_size field to it and we have no
+ * free space in the current mft record.
+ */
+ if ((le32_to_cpu(a->length) -
+ le16_to_cpu(a->mapping_pairs_offset) == 8)
+ && !(le32_to_cpu(m->bytes_allocated) -
+ le32_to_cpu(m->bytes_in_use))) {
+
+ if (!NInoAttrList(na->ni)) {
+ ntfs_attr_put_search_ctx(ctx);
+ if (ntfs_inode_add_attrlist(na->ni))
+ goto leave;
+ goto retry;
+ }
+ if (ntfs_attr_record_move_away(ctx, 8)) {
+ ntfs_log_perror("Failed to move attribute");
+ goto error;
+ }
+ ntfs_attr_put_search_ctx(ctx);
+ goto retry;
+ }
+ if (!(le32_to_cpu(a->length) - le16_to_cpu(
+ a->mapping_pairs_offset))) {
+ errno = EIO;
+ ntfs_log_perror("Mapping pairs space is 0");
+ goto error;
+ }
+
+ NAttrSetSparse(na);
+ a->flags |= ATTR_IS_SPARSE;
+ a->compression_unit = STANDARD_COMPRESSION_UNIT; /* Windows
+ set it so, even if attribute is not actually compressed. */
+
+ memmove((u8*)a + le16_to_cpu(a->name_offset) + 8,
+ (u8*)a + le16_to_cpu(a->name_offset),
+ a->name_length * sizeof(ntfschar));
+
+ a->name_offset = cpu_to_le16(le16_to_cpu(a->name_offset) + 8);
+
+ a->mapping_pairs_offset =
+ cpu_to_le16(le16_to_cpu(a->mapping_pairs_offset) + 8);
+ }
+
+ /* Attribute no longer sparse. */
+ if (!sparse && (a->flags & ATTR_IS_SPARSE) &&
+ !(a->flags & ATTR_IS_COMPRESSED)) {
+
+ NAttrClearSparse(na);
+ a->flags &= ~ATTR_IS_SPARSE;
+ a->compression_unit = 0;
+
+ memmove((u8*)a + le16_to_cpu(a->name_offset) - 8,
+ (u8*)a + le16_to_cpu(a->name_offset),
+ a->name_length * sizeof(ntfschar));
+
+ if (le16_to_cpu(a->name_offset) >= 8)
+ a->name_offset = cpu_to_le16(le16_to_cpu(a->name_offset) - 8);
+
+ a->mapping_pairs_offset =
+ cpu_to_le16(le16_to_cpu(a->mapping_pairs_offset) - 8);
+ }
+
+ /* Update compressed size if required. */
+ if (sparse || (na->data_flags & ATTR_COMPRESSION_MASK)) {
+ s64 new_compr_size;
+
+ new_compr_size = ntfs_rl_get_compressed_size(na->ni->vol, na->rl);
+ if (new_compr_size == -1)
+ goto error;
+
+ na->compressed_size = new_compr_size;
+ a->compressed_size = cpu_to_sle64(new_compr_size);
+ }
+ /*
+ * Set FILE_NAME dirty flag, to update sparse bit and
+ * allocated size in the index.
+ */
+ if (na->type == AT_DATA && na->name == AT_UNNAMED) {
+ if (sparse)
+ na->ni->allocated_size = na->compressed_size;
+ else
+ na->ni->allocated_size = na->allocated_size;
+ NInoFileNameSetDirty(na->ni);
+ }
+out:
+ return ret;
+leave: ret = -1; goto out; /* return -1 */
+retry: ret = -2; goto out;
+error: ret = -3; goto out;
+}
+
+#define NTFS_VCN_DELETE_MARK -2
+/**
+ * ntfs_attr_update_mapping_pairs_i - see ntfs_attr_update_mapping_pairs
+ */
+static int ntfs_attr_update_mapping_pairs_i(ntfs_attr *na, VCN from_vcn)
+{
+ ntfs_attr_search_ctx *ctx;
+ ntfs_inode *ni, *base_ni;
+ MFT_RECORD *m;
+ ATTR_RECORD *a;
+ VCN stop_vcn;
+ const runlist_element *stop_rl;
+ int err, mp_size, cur_max_mp_size, exp_max_mp_size, ret = -1;
+ BOOL finished_build;
+
+retry:
+ if (!na || !na->rl || from_vcn) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: na=%p", __FUNCTION__, na);
+ return -1;
+ }
+
+ ntfs_log_trace("Entering for inode %llu, attr 0x%x\n",
+ (unsigned long long)na->ni->mft_no, na->type);
+
+ if (!NAttrNonResident(na)) {
+ errno = EINVAL;
+ ntfs_log_perror("%s: resident attribute", __FUNCTION__);
+ return -1;
+ }
+
+ if (na->ni->nr_extents == -1)
+ base_ni = na->ni->base_ni;
+ else
+ base_ni = na->ni;
+
+ ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
+ if (!ctx)
+ return -1;
+
+ /* Fill attribute records with new mapping pairs. */
+ stop_vcn = 0;
+ stop_rl = na->rl;
+ finished_build = FALSE;
+ while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
+ CASE_SENSITIVE, from_vcn, NULL, 0, ctx)) {
+ a = ctx->attr;
+ m = ctx->mrec;
+ /*
+ * If runlist is updating not from the beginning, then set
+ * @stop_vcn properly, i.e. to the lowest vcn of record that
+ * contain @from_vcn. Also we do not need @from_vcn anymore,
+ * set it to 0 to make ntfs_attr_lookup enumerate attributes.
+ */
+ if (from_vcn) {
+ LCN first_lcn;
+
+ stop_vcn = sle64_to_cpu(a->lowest_vcn);
+ from_vcn = 0;
+ /*
+ * Check whether the first run we need to update is
+ * the last run in runlist, if so, then deallocate
+ * all attrubute extents starting this one.
+ */
+ first_lcn = ntfs_rl_vcn_to_lcn(na->rl, stop_vcn);
+ if (first_lcn == LCN_EINVAL) {
+ errno = EIO;
+ ntfs_log_perror("Bad runlist");
+ goto put_err_out;
+ }
+ if (first_lcn == LCN_ENOENT ||
+ first_lcn == LCN_RL_NOT_MAPPED)
+ finished_build = TRUE;
+ }
+
+ /*
+ * Check whether we finished mapping pairs build, if so mark
+ * extent as need to delete (by setting highest vcn to
+ * NTFS_VCN_DELETE_MARK (-2), we shall check it later and
+ * delete extent) and continue search.
+ */
+ if (finished_build) {
+ ntfs_log_trace("Mark attr 0x%x for delete in inode "
+ "%lld.\n", (unsigned)le32_to_cpu(a->type),
+ (long long)ctx->ntfs_ino->mft_no);
+ a->highest_vcn = cpu_to_sle64(NTFS_VCN_DELETE_MARK);
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ continue;
+ }
+
+ switch (ntfs_attr_update_meta(a, na, m, ctx)) {
+ case -1: return -1;
+ case -2: goto retry;
+ case -3: goto put_err_out;
+ }
+
+ /*
+ * Determine maximum possible length of mapping pairs,
+ * if we shall *not* expand space for mapping pairs.
+ */
+ cur_max_mp_size = le32_to_cpu(a->length) -
+ le16_to_cpu(a->mapping_pairs_offset);
+ /*
+ * Determine maximum possible length of mapping pairs in the
+ * current mft record, if we shall expand space for mapping
+ * pairs.
+ */
+ exp_max_mp_size = le32_to_cpu(m->bytes_allocated) -
+ le32_to_cpu(m->bytes_in_use) + cur_max_mp_size;
+ /* Get the size for the rest of mapping pairs array. */
+ mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol, stop_rl,
+ stop_vcn, exp_max_mp_size);
+ if (mp_size <= 0) {
+ ntfs_log_perror("%s: get MP size failed", __FUNCTION__);
+ goto put_err_out;
+ }
+ /* Test mapping pairs for fitting in the current mft record. */
+ if (mp_size > exp_max_mp_size) {
+ /*
+ * Mapping pairs of $ATTRIBUTE_LIST attribute must fit
+ * in the base mft record. Try to move out other
+ * attributes and try again.
+ */
+ if (na->type == AT_ATTRIBUTE_LIST) {
+ ntfs_attr_put_search_ctx(ctx);
+ if (ntfs_inode_free_space(na->ni, mp_size -
+ cur_max_mp_size)) {
+ ntfs_log_perror("Attribute list is too "
+ "big. Defragment the "
+ "volume\n");
+ return -1;
+ }
+ goto retry;
+ }
+
+ /* Add attribute list if it isn't present, and retry. */
+ if (!NInoAttrList(base_ni)) {
+ ntfs_attr_put_search_ctx(ctx);
+ if (ntfs_inode_add_attrlist(base_ni)) {
+ ntfs_log_perror("Can not add attrlist");
+ return -1;
+ }
+ goto retry;
+ }
+
+ /*
+ * Set mapping pairs size to maximum possible for this
+ * mft record. We shall write the rest of mapping pairs
+ * to another MFT records.
+ */
+ mp_size = exp_max_mp_size;
+ }
+
+ /* Change space for mapping pairs if we need it. */
+ if (((mp_size + 7) & ~7) != cur_max_mp_size) {
+ if (ntfs_attr_record_resize(m, a,
+ le16_to_cpu(a->mapping_pairs_offset) +
+ mp_size)) {
+ errno = EIO;
+ ntfs_log_perror("Failed to resize attribute");
+ goto put_err_out;
+ }
+ }
+
+ /* Update lowest vcn. */
+ a->lowest_vcn = cpu_to_sle64(stop_vcn);
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ if ((ctx->ntfs_ino->nr_extents == -1 ||
+ NInoAttrList(ctx->ntfs_ino)) &&
+ ctx->attr->type != AT_ATTRIBUTE_LIST) {
+ ctx->al_entry->lowest_vcn = cpu_to_sle64(stop_vcn);
+ ntfs_attrlist_mark_dirty(ctx->ntfs_ino);
+ }
+
+ /*
+ * Generate the new mapping pairs array directly into the
+ * correct destination, i.e. the attribute record itself.
+ */
+ if (!ntfs_mapping_pairs_build(na->ni->vol, (u8*)a + le16_to_cpu(
+ a->mapping_pairs_offset), mp_size, na->rl,
+ stop_vcn, &stop_rl))
+ finished_build = TRUE;
+ if (stop_rl)
+ stop_vcn = stop_rl->vcn;
+ else
+ stop_vcn = 0;
+ if (!finished_build && errno != ENOSPC) {
+ ntfs_log_perror("Failed to build mapping pairs");
+ goto put_err_out;
+ }
+ a->highest_vcn = cpu_to_sle64(stop_vcn - 1);
+ }
+ /* Check whether error occurred. */
+ if (errno != ENOENT) {
+ ntfs_log_perror("%s: Attribute lookup failed", __FUNCTION__);
+ goto put_err_out;
+ }
+
+ /* Deallocate not used attribute extents and return with success. */
+ if (finished_build) {
+ ntfs_attr_reinit_search_ctx(ctx);
+ ntfs_log_trace("Deallocate marked extents.\n");
+ while (!ntfs_attr_lookup(na->type, na->name, na->name_len,
+ CASE_SENSITIVE, 0, NULL, 0, ctx)) {
+ if (sle64_to_cpu(ctx->attr->highest_vcn) !=
+ NTFS_VCN_DELETE_MARK)
+ continue;
+ /* Remove unused attribute record. */
+ if (ntfs_attr_record_rm(ctx)) {
+ ntfs_log_perror("Could not remove unused attr");
+ goto put_err_out;
+ }
+ ntfs_attr_reinit_search_ctx(ctx);
+ }
+ if (errno != ENOENT) {
+ ntfs_log_perror("%s: Attr lookup failed", __FUNCTION__);
+ goto put_err_out;
+ }
+ ntfs_log_trace("Deallocate done.\n");
+ ntfs_attr_put_search_ctx(ctx);
+ goto ok;
+ }
+ ntfs_attr_put_search_ctx(ctx);
+ ctx = NULL;
+
+ /* Allocate new MFT records for the rest of mapping pairs. */
+ while (1) {
+ /* Calculate size of rest mapping pairs. */
+ mp_size = ntfs_get_size_for_mapping_pairs(na->ni->vol,
+ na->rl, stop_vcn, INT_MAX);
+ if (mp_size <= 0) {
+ ntfs_log_perror("%s: get mp size failed", __FUNCTION__);
+ goto put_err_out;
+ }
+ /* Allocate new mft record. */
+ ni = ntfs_mft_record_alloc(na->ni->vol, base_ni);
+ if (!ni) {
+ ntfs_log_perror("Could not allocate new MFT record");
+ goto put_err_out;
+ }
+ m = ni->mrec;
+ /*
+ * If mapping size exceed available space, set them to
+ * possible maximum.
+ */
+ cur_max_mp_size = le32_to_cpu(m->bytes_allocated) -
+ le32_to_cpu(m->bytes_in_use) -
+ (offsetof(ATTR_RECORD, compressed_size) +
+ (((na->data_flags & ATTR_COMPRESSION_MASK)
+ || NAttrSparse(na)) ?
+ sizeof(a->compressed_size) : 0)) -
+ ((sizeof(ntfschar) * na->name_len + 7) & ~7);
+ if (mp_size > cur_max_mp_size)
+ mp_size = cur_max_mp_size;
+ /* Add attribute extent to new record. */
+ err = ntfs_non_resident_attr_record_add(ni, na->type,
+ na->name, na->name_len, stop_vcn, mp_size,
+ na->data_flags);
+ if (err == -1) {
+ err = errno;
+ ntfs_log_perror("Could not add attribute extent");
+ if (ntfs_mft_record_free(na->ni->vol, ni))
+ ntfs_log_perror("Could not free MFT record");
+ errno = err;
+ goto put_err_out;
+ }
+ a = (ATTR_RECORD*)((u8*)m + err);
+
+ err = ntfs_mapping_pairs_build(na->ni->vol, (u8*)a +
+ le16_to_cpu(a->mapping_pairs_offset), mp_size, na->rl,
+ stop_vcn, &stop_rl);
+ if (stop_rl)
+ stop_vcn = stop_rl->vcn;
+ else
+ stop_vcn = 0;
+ if (err < 0 && errno != ENOSPC) {
+ err = errno;
+ ntfs_log_perror("Failed to build MP");
+ if (ntfs_mft_record_free(na->ni->vol, ni))
+ ntfs_log_perror("Couldn't free MFT record");
+ errno = err;
+ goto put_err_out;
+ }
+ a->highest_vcn = cpu_to_sle64(stop_vcn - 1);
+ ntfs_inode_mark_dirty(ni);
+ /* All mapping pairs has been written. */
+ if (!err)
+ break;
+ }
+ok:
+ ret = 0;
+out:
+ return ret;
+put_err_out:
+ if (ctx)
+ ntfs_attr_put_search_ctx(ctx);
+ goto out;
+}
+#undef NTFS_VCN_DELETE_MARK
+
+/**
+ * ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute
+ * @na: non-resident ntfs open attribute for which we need update
+ * @from_vcn: update runlist starting this VCN
+ *
+ * Build mapping pairs from @na->rl and write them to the disk. Also, this
+ * function updates sparse bit, allocated and compressed size (allocates/frees
+ * space for this field if required).
+ *
+ * @na->allocated_size should be set to correct value for the new runlist before
+ * call to this function. Vice-versa @na->compressed_size will be calculated and
+ * set to correct value during this function.
+ *
+ * FIXME: This function does not update sparse bit and compressed size correctly
+ * if called with @from_vcn != 0.
+ *
+ * FIXME: Rewrite without using NTFS_VCN_DELETE_MARK define.
+ *
+ * On success return 0 and on error return -1 with errno set to the error code.
+ * The following error codes are defined:
+ * EINVAL - Invalid arguments passed.
+ * ENOMEM - Not enough memory to complete operation.
+ * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST
+ * or there is no free MFT records left to allocate.
+ */
+int ntfs_attr_update_mapping_pairs(ntfs_attr *na, VCN from_vcn)
+{
+ int ret;
+
+ ntfs_log_enter("Entering\n");
+ ret = ntfs_attr_update_mapping_pairs_i(na, from_vcn);
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+/**
+ * ntfs_non_resident_attr_shrink - shrink a non-resident, open ntfs attribute
+ * @na: non-resident ntfs attribute to shrink
+ * @newsize: new size (in bytes) to which to shrink the attribute
+ *
+ * Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes.
+ *
+ * On success return 0 and on error return -1 with errno set to the error code.
+ * The following error codes are defined:
+ * ENOMEM - Not enough memory to complete operation.
+ * ERANGE - @newsize is not valid for the attribute type of @na.
+ */
+static int ntfs_non_resident_attr_shrink(ntfs_attr *na, const s64 newsize)
+{
+ ntfs_volume *vol;
+ ntfs_attr_search_ctx *ctx;
+ VCN first_free_vcn;
+ s64 nr_freed_clusters;
+ int err;
+
+ ntfs_log_trace("Inode 0x%llx attr 0x%x new size %lld\n", (unsigned long long)
+ na->ni->mft_no, na->type, (long long)newsize);
+
+ vol = na->ni->vol;
+
+ /*
+ * Check the attribute type and the corresponding minimum size
+ * against @newsize and fail if @newsize is too small.
+ */
+ if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) {
+ if (errno == ERANGE) {
+ ntfs_log_trace("Eeek! Size bounds check failed. "
+ "Aborting...\n");
+ } else if (errno == ENOENT)
+ errno = EIO;
+ return -1;
+ }
+
+ /* The first cluster outside the new allocation. */
+ first_free_vcn = (newsize + vol->cluster_size - 1) >>
+ vol->cluster_size_bits;
+ /*
+ * Compare the new allocation with the old one and only deallocate
+ * clusters if there is a change.
+ */
+ if ((na->allocated_size >> vol->cluster_size_bits) != first_free_vcn) {
+ if (ntfs_attr_map_whole_runlist(na)) {
+ ntfs_log_trace("Eeek! ntfs_attr_map_whole_runlist "
+ "failed.\n");
+ return -1;
+ }
+ /* Deallocate all clusters starting with the first free one. */
+ nr_freed_clusters = ntfs_cluster_free(vol, na, first_free_vcn,
+ -1);
+ if (nr_freed_clusters < 0) {
+ ntfs_log_trace("Eeek! Freeing of clusters failed. "
+ "Aborting...\n");
+ return -1;
+ }
+
+ /* Truncate the runlist itself. */
+ if (ntfs_rl_truncate(&na->rl, first_free_vcn)) {
+ /*
+ * Failed to truncate the runlist, so just throw it
+ * away, it will be mapped afresh on next use.
+ */
+ free(na->rl);
+ na->rl = NULL;
+ ntfs_log_trace("Eeek! Run list truncation failed.\n");
+ return -1;
+ }
+
+ /* Prepare to mapping pairs update. */
+ na->allocated_size = first_free_vcn << vol->cluster_size_bits;
+ /* Write mapping pairs for new runlist. */
+ if (ntfs_attr_update_mapping_pairs(na, 0 /*first_free_vcn*/)) {
+ ntfs_log_trace("Eeek! Mapping pairs update failed. "
+ "Leaving inconstant metadata. "
+ "Run chkdsk.\n");
+ return -1;
+ }
+ }
+
+ /* Get the first attribute record. */
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx)
+ return -1;
+
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
+ 0, NULL, 0, ctx)) {
+ err = errno;
+ if (err == ENOENT)
+ err = EIO;
+ ntfs_log_trace("Eeek! Lookup of first attribute extent failed. "
+ "Leaving inconstant metadata.\n");
+ goto put_err_out;
+ }
+
+ /* Update data and initialized size. */
+ na->data_size = newsize;
+ ctx->attr->data_size = cpu_to_sle64(newsize);
+ if (newsize < na->initialized_size) {
+ na->initialized_size = newsize;
+ ctx->attr->initialized_size = cpu_to_sle64(newsize);
+ }
+ /* Update data size in the index. */
+ if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ if (na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30) {
+ na->ni->data_size = na->data_size;
+ na->ni->allocated_size = na->allocated_size;
+ set_nino_flag(na->ni,KnownSize);
+ }
+ } else {
+ if (na->type == AT_DATA && na->name == AT_UNNAMED) {
+ na->ni->data_size = na->data_size;
+ NInoFileNameSetDirty(na->ni);
+ }
+ }
+
+ /* If the attribute now has zero size, make it resident. */
+ if (!newsize) {
+ if (ntfs_attr_make_resident(na, ctx)) {
+ /* If couldn't make resident, just continue. */
+ if (errno != EPERM)
+ ntfs_log_error("Failed to make attribute "
+ "resident. Leaving as is...\n");
+ }
+ }
+
+ /* Set the inode dirty so it is written out later. */
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ /* Done! */
+ ntfs_attr_put_search_ctx(ctx);
+ return 0;
+put_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+ errno = err;
+ return -1;
+}
+
+/**
+ * ntfs_non_resident_attr_expand - expand a non-resident, open ntfs attribute
+ * @na: non-resident ntfs attribute to expand
+ * @newsize: new size (in bytes) to which to expand the attribute
+ *
+ * Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes,
+ * by allocating new clusters.
+ *
+ * On success return 0 and on error return -1 with errno set to the error code.
+ * The following error codes are defined:
+ * ENOMEM - Not enough memory to complete operation.
+ * ERANGE - @newsize is not valid for the attribute type of @na.
+ * ENOSPC - There is no enough space in base mft to resize $ATTRIBUTE_LIST.
+ */
+static int ntfs_non_resident_attr_expand_i(ntfs_attr *na, const s64 newsize)
+{
+ LCN lcn_seek_from;
+ VCN first_free_vcn;
+ ntfs_volume *vol;
+ ntfs_attr_search_ctx *ctx;
+ runlist *rl, *rln;
+ s64 org_alloc_size;
+ int err;
+
+ ntfs_log_trace("Inode %lld, attr 0x%x, new size %lld old size %lld\n",
+ (unsigned long long)na->ni->mft_no, na->type,
+ (long long)newsize, (long long)na->data_size);
+
+ vol = na->ni->vol;
+
+ /*
+ * Check the attribute type and the corresponding maximum size
+ * against @newsize and fail if @newsize is too big.
+ */
+ if (ntfs_attr_size_bounds_check(vol, na->type, newsize) < 0) {
+ if (errno == ENOENT)
+ errno = EIO;
+ ntfs_log_perror("%s: bounds check failed", __FUNCTION__);
+ return -1;
+ }
+
+ /* Save for future use. */
+ org_alloc_size = na->allocated_size;
+ /* The first cluster outside the new allocation. */
+ first_free_vcn = (newsize + vol->cluster_size - 1) >>
+ vol->cluster_size_bits;
+ /*
+ * Compare the new allocation with the old one and only allocate
+ * clusters if there is a change.
+ */
+ if ((na->allocated_size >> vol->cluster_size_bits) < first_free_vcn) {
+ if (ntfs_attr_map_whole_runlist(na)) {
+ ntfs_log_perror("ntfs_attr_map_whole_runlist failed");
+ return -1;
+ }
+
+ /*
+ * If we extend $DATA attribute on NTFS 3+ volume, we can add
+ * sparse runs instead of real allocation of clusters.
+ */
+ if (na->type == AT_DATA && vol->major_ver >= 3) {
+ rl = ntfs_malloc(0x1000);
+ if (!rl)
+ return -1;
+
+ rl[0].vcn = (na->allocated_size >>
+ vol->cluster_size_bits);
+ rl[0].lcn = LCN_HOLE;
+ rl[0].length = first_free_vcn -
+ (na->allocated_size >> vol->cluster_size_bits);
+ rl[1].vcn = first_free_vcn;
+ rl[1].lcn = LCN_ENOENT;
+ rl[1].length = 0;
+ } else {
+ /*
+ * Determine first after last LCN of attribute.
+ * We will start seek clusters from this LCN to avoid
+ * fragmentation. If there are no valid LCNs in the
+ * attribute let the cluster allocator choose the
+ * starting LCN.
+ */
+ lcn_seek_from = -1;
+ if (na->rl->length) {
+ /* Seek to the last run list element. */
+ for (rl = na->rl; (rl + 1)->length; rl++)
+ ;
+ /*
+ * If the last LCN is a hole or similar seek
+ * back to last valid LCN.
+ */
+ while (rl->lcn < 0 && rl != na->rl)
+ rl--;
+ /*
+ * Only set lcn_seek_from it the LCN is valid.
+ */
+ if (rl->lcn >= 0)
+ lcn_seek_from = rl->lcn + rl->length;
+ }
+
+ rl = ntfs_cluster_alloc(vol, na->allocated_size >>
+ vol->cluster_size_bits, first_free_vcn -
+ (na->allocated_size >>
+ vol->cluster_size_bits), lcn_seek_from,
+ DATA_ZONE);
+ if (!rl) {
+ ntfs_log_perror("Cluster allocation failed "
+ "(%lld)",
+ (long long)first_free_vcn -
+ ((long long)na->allocated_size >>
+ vol->cluster_size_bits));
+ return -1;
+ }
+ }
+
+ /* Append new clusters to attribute runlist. */
+ rln = ntfs_runlists_merge(na->rl, rl);
+ if (!rln) {
+ /* Failed, free just allocated clusters. */
+ err = errno;
+ ntfs_log_perror("Run list merge failed");
+ ntfs_cluster_free_from_rl(vol, rl);
+ free(rl);
+ errno = err;
+ return -1;
+ }
+ na->rl = rln;
+
+ /* Prepare to mapping pairs update. */
+ na->allocated_size = first_free_vcn << vol->cluster_size_bits;
+ /* Write mapping pairs for new runlist. */
+ if (ntfs_attr_update_mapping_pairs(na, 0 /*na->allocated_size >>
+ vol->cluster_size_bits*/)) {
+ err = errno;
+ ntfs_log_perror("Mapping pairs update failed");
+ goto rollback;
+ }
+ }
+
+ ctx = ntfs_attr_get_search_ctx(na->ni, NULL);
+ if (!ctx) {
+ err = errno;
+ if (na->allocated_size == org_alloc_size) {
+ errno = err;
+ return -1;
+ } else
+ goto rollback;
+ }
+
+ if (ntfs_attr_lookup(na->type, na->name, na->name_len, CASE_SENSITIVE,
+ 0, NULL, 0, ctx)) {
+ err = errno;
+ ntfs_log_perror("Lookup of first attribute extent failed");
+ if (err == ENOENT)
+ err = EIO;
+ if (na->allocated_size != org_alloc_size) {
+ ntfs_attr_put_search_ctx(ctx);
+ goto rollback;
+ } else
+ goto put_err_out;
+ }
+
+ /* Update data size. */
+ na->data_size = newsize;
+ ctx->attr->data_size = cpu_to_sle64(newsize);
+ /* Update data size in the index. */
+ if (na->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
+ if (na->type == AT_INDEX_ROOT && na->name == NTFS_INDEX_I30) {
+ na->ni->data_size = na->data_size;
+ na->ni->allocated_size = na->allocated_size;
+ set_nino_flag(na->ni,KnownSize);
+ }
+ } else {
+ if (na->type == AT_DATA && na->name == AT_UNNAMED) {
+ na->ni->data_size = na->data_size;
+ NInoFileNameSetDirty(na->ni);
+ }
+ }
+ /* Set the inode dirty so it is written out later. */
+ ntfs_inode_mark_dirty(ctx->ntfs_ino);
+ /* Done! */
+ ntfs_attr_put_search_ctx(ctx);
+ return 0;
+rollback:
+ /* Free allocated clusters. */
+ if (ntfs_cluster_free(vol, na, org_alloc_size >>
+ vol->cluster_size_bits, -1) < 0) {
+ err = EIO;
+ ntfs_log_perror("Leaking clusters");
+ }
+ /* Now, truncate the runlist itself. */
+ if (ntfs_rl_truncate(&na->rl, org_alloc_size >>
+ vol->cluster_size_bits)) {
+ /*
+ * Failed to truncate the runlist, so just throw it away, it
+ * will be mapped afresh on next use.
+ */
+ free(na->rl);
+ na->rl = NULL;
+ ntfs_log_perror("Couldn't truncate runlist. Rollback failed");
+ } else {
+ /* Prepare to mapping pairs update. */
+ na->allocated_size = org_alloc_size;
+ /* Restore mapping pairs. */
+ if (ntfs_attr_update_mapping_pairs(na, 0 /*na->allocated_size >>
+ vol->cluster_size_bits*/)) {
+ ntfs_log_perror("Failed to restore old mapping pairs");
+ }
+ }
+ errno = err;
+ return -1;
+put_err_out:
+ ntfs_attr_put_search_ctx(ctx);
+ errno = err;
+ return -1;
+}
+
+
+static int ntfs_non_resident_attr_expand(ntfs_attr *na, const s64 newsize)
+{
+ int ret;
+
+ ntfs_log_enter("Entering\n");
+ ret = ntfs_non_resident_attr_expand_i(na, newsize);
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+/**
+ * ntfs_attr_truncate - resize an ntfs attribute
+ * @na: open ntfs attribute to resize
+ * @newsize: new size (in bytes) to which to resize the attribute
+ *
+ * Change the size of an open ntfs attribute @na to @newsize bytes. If the
+ * attribute is made bigger and the attribute is resident the newly
+ * "allocated" space is cleared and if the attribute is non-resident the
+ * newly allocated space is marked as not initialised and no real allocation
+ * on disk is performed.
+ *
+ * On success return 0.
+ * On error return values are:
+ * STATUS_RESIDENT_ATTRIBUTE_FILLED_MFT
+ * STATUS_ERROR - otherwise
+ * The following error codes are defined:
+ * EINVAL - Invalid arguments were passed to the function.
+ * EOPNOTSUPP - The desired resize is not implemented yet.
+ * EACCES - Encrypted attribute.
+ */
+int ntfs_attr_truncate(ntfs_attr *na, const s64 newsize)
+{
+ int ret = STATUS_ERROR;
+ s64 fullsize;
+ BOOL compressed;
+
+ if (!na || newsize < 0 ||
+ (na->ni->mft_no == FILE_MFT && na->type == AT_DATA)) {
+ ntfs_log_trace("Invalid arguments passed.\n");
+ errno = EINVAL;
+ return STATUS_ERROR;
+ }
+
+ ntfs_log_enter("Entering for inode %lld, attr 0x%x, size %lld\n",
+ (unsigned long long)na->ni->mft_no, na->type,
+ (long long)newsize);
+
+ if (na->data_size == newsize) {
+ ntfs_log_trace("Size is already ok\n");
+ ret = STATUS_OK;
+ goto out;
+ }
+ /*
+ * Encrypted attributes are not supported. We return access denied,
+ * which is what Windows NT4 does, too.
+ */
+ if (na->data_flags & ATTR_IS_ENCRYPTED) {
+ errno = EACCES;
+ ntfs_log_trace("Cannot truncate encrypted attribute\n");
+ goto out;
+ }
+ /*
+ * TODO: Implement making handling of compressed attributes.
+ * Currently we can only expand the attribute or delete it,
+ * and only for ATTR_IS_COMPRESSED. This is however possible
+ * for resident attributes when there is no open fuse context
+ * (important case : $INDEX_ROOT:$I30)
+ */
+ compressed = (na->data_flags & ATTR_COMPRESSION_MASK)
+ != const_cpu_to_le16(0);
+ if (compressed
+ && NAttrNonResident(na)
+ && (((na->data_flags & ATTR_COMPRESSION_MASK) != ATTR_IS_COMPRESSED)
+ || (newsize && (newsize < na->data_size)))) {
+ errno = EOPNOTSUPP;
+ ntfs_log_perror("Failed to truncate compressed attribute");
+ goto out;
+ }
+ if (NAttrNonResident(na)) {
+ /*
+ * For compressed data, the last block must be fully
+ * allocated, and we do not known the size of compression
+ * block until the attribute has been made non-resident.
+ * Moreover we can only process a single compression
+ * block at a time (from where we are about to write),
+ * so we silently do not allocate more.
+ *
+ * Note : do not request truncate on compressed files
+ * unless being able to face the consequences !
+ */
+ if (compressed && newsize)
+ fullsize = (na->initialized_size
+ | (na->compression_block_size - 1)) + 1;
+ else
+ fullsize = newsize;
+ if (fullsize > na->data_size)
+ ret = ntfs_non_resident_attr_expand(na, fullsize);
+ else
+ ret = ntfs_non_resident_attr_shrink(na, fullsize);
+ } else
+ ret = ntfs_resident_attr_resize(na, newsize);
+out:
+ ntfs_log_leave("Return status %d\n", ret);
+ return ret;
+}
+
+/*
+ * Stuff a hole in a compressed file
+ *
+ * An unallocated hole must be aligned on compression block size.
+ * If needed current block and target block are stuffed with zeroes.
+ *
+ * Returns 0 if succeeded,
+ * -1 if it failed (as explained in errno)
+ */
+
+static int stuff_hole(ntfs_attr *na, const s64 pos)
+{
+ s64 size;
+ s64 begin_size;
+ s64 end_size;
+ char *buf;
+ int ret;
+
+ ret = 0;
+ /*
+ * If the attribute is resident, the compression block size
+ * is not defined yet and we can make no decision.
+ * So we first try resizing to the target and if the
+ * attribute is still resident, we're done
+ */
+ if (!NAttrNonResident(na)) {
+ ret = ntfs_resident_attr_resize(na, pos);
+ if (!ret && !NAttrNonResident(na))
+ na->initialized_size = na->data_size = pos;
+ }
+ if (!ret && NAttrNonResident(na)) {
+ /* does the hole span over several compression block ? */
+ if ((pos ^ na->initialized_size)
+ & ~(na->compression_block_size - 1)) {
+ begin_size = ((na->initialized_size - 1)
+ | (na->compression_block_size - 1))
+ + 1 - na->initialized_size;
+ end_size = pos & (na->compression_block_size - 1);
+ size = (begin_size > end_size ? begin_size : end_size);
+ } else {
+ /* short stuffing in a single compression block */
+ begin_size = size = pos - na->initialized_size;
+ end_size = 0;
+ }
+ if (size)
+ buf = (char*)ntfs_malloc(size);
+ else
+ buf = (char*)NULL;
+ if (buf || !size) {
+ memset(buf,0,size);
+ /* stuff into current block */
+ if (begin_size
+ && (ntfs_attr_pwrite(na,
+ na->initialized_size, begin_size, buf)
+ != begin_size))
+ ret = -1;
+ /* create an unstuffed hole */
+ if (!ret
+ && ((na->initialized_size + end_size) < pos)
+ && ntfs_non_resident_attr_expand(na,
+ pos - end_size))
+ ret = -1;
+ else
+ na->initialized_size
+ = na->data_size = pos - end_size;
+ /* stuff into the target block */
+ if (!ret && end_size
+ && (ntfs_attr_pwrite(na,
+ na->initialized_size, end_size, buf)
+ != end_size))
+ ret = -1;
+ if (buf)
+ free(buf);
+ } else
+ ret = -1;
+ }
+ /* make absolutely sure we have reached the target */
+ if (!ret && (na->initialized_size != pos)) {
+ ntfs_log_error("Failed to stuff a compressed file"
+ "target %lld reached %lld\n",
+ (long long)pos, (long long)na->initialized_size);
+ errno = EIO;
+ ret = -1;
+ }
+ return (ret);
+}
+
+/**
+ * ntfs_attr_readall - read the entire data from an ntfs attribute
+ * @ni: open ntfs inode in which the ntfs attribute resides
+ * @type: attribute type
+ * @name: attribute name in little endian Unicode or AT_UNNAMED or NULL
+ * @name_len: length of attribute @name in Unicode characters (if @name given)
+ * @data_size: if non-NULL then store here the data size
+ *
+ * This function will read the entire content of an ntfs attribute.
+ * If @name is AT_UNNAMED then look specifically for an unnamed attribute.
+ * If @name is NULL then the attribute could be either named or not.
+ * In both those cases @name_len is not used at all.
+ *
+ * On success a buffer is allocated with the content of the attribute
+ * and which needs to be freed when it's not needed anymore. If the
+ * @data_size parameter is non-NULL then the data size is set there.
+ *
+ * On error NULL is returned with errno set to the error code.
+ */
+void *ntfs_attr_readall(ntfs_inode *ni, const ATTR_TYPES type,
+ ntfschar *name, u32 name_len, s64 *data_size)
+{
+ ntfs_attr *na;
+ void *data, *ret = NULL;
+ s64 size;
+
+ ntfs_log_enter("Entering\n");
+
+ na = ntfs_attr_open(ni, type, name, name_len);
+ if (!na) {
+ ntfs_log_perror("ntfs_attr_open failed");
+ goto err_exit;
+ }
+ data = ntfs_malloc(na->data_size);
+ if (!data)
+ goto out;
+
+ size = ntfs_attr_pread(na, 0, na->data_size, data);
+ if (size != na->data_size) {
+ ntfs_log_perror("ntfs_attr_pread failed");
+ free(data);
+ goto out;
+ }
+ ret = data;
+ if (data_size)
+ *data_size = size;
+out:
+ ntfs_attr_close(na);
+err_exit:
+ ntfs_log_leave("\n");
+ return ret;
+}
+
+
+
+int ntfs_attr_exist(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name,
+ u32 name_len)
+{
+ ntfs_attr_search_ctx *ctx;
+ int ret;
+
+ ntfs_log_trace("Entering\n");
+
+ ctx = ntfs_attr_get_search_ctx(ni, NULL);
+ if (!ctx)
+ return 0;
+
+ ret = ntfs_attr_lookup(type, name, name_len, CASE_SENSITIVE, 0, NULL, 0,
+ ctx);
+
+ ntfs_attr_put_search_ctx(ctx);
+
+ return !ret;
+}
+
+int ntfs_attr_remove(ntfs_inode *ni, const ATTR_TYPES type, ntfschar *name,
+ u32 name_len)
+{
+ ntfs_attr *na;
+ int ret;
+
+ ntfs_log_trace("Entering\n");
+
+ if (!ni) {
+ ntfs_log_error("%s: NULL inode pointer", __FUNCTION__);
+ errno = EINVAL;
+ return -1;
+ }
+
+ na = ntfs_attr_open(ni, type, name, name_len);
+ if (!na) {
+ /* do not log removal of non-existent stream */
+ if (type != AT_DATA) {
+ ntfs_log_perror("Failed to open attribute 0x%02x of inode "
+ "0x%llx", type, (unsigned long long)ni->mft_no);
+ }
+ return -1;
+ }
+
+ ret = ntfs_attr_rm(na);
+ if (ret)
+ ntfs_log_perror("Failed to remove attribute 0x%02x of inode "
+ "0x%llx", type, (unsigned long long)ni->mft_no);
+ ntfs_attr_close(na);
+
+ return ret;
+}
+
+/* Below macros are 32-bit ready. */
+#define BCX(x) ((x) - (((x) >> 1) & 0x77777777) - \
+ (((x) >> 2) & 0x33333333) - \
+ (((x) >> 3) & 0x11111111))
+#define BITCOUNT(x) (((BCX(x) + (BCX(x) >> 4)) & 0x0F0F0F0F) % 255)
+
+static u8 *ntfs_init_lut256(void)
+{
+ int i;
+ u8 *lut;
+
+ lut = ntfs_malloc(256);
+ if (lut)
+ for(i = 0; i < 256; i++)
+ *(lut + i) = 8 - BITCOUNT(i);
+ return lut;
+}
+
+s64 ntfs_attr_get_free_bits(ntfs_attr *na)
+{
+ u8 *buf, *lut;
+ s64 br = 0;
+ s64 total = 0;
+ s64 nr_free = 0;
+
+ lut = ntfs_init_lut256();
+ if (!lut)
+ return -1;
+
+ buf = ntfs_malloc(65536);
+ if (!buf)
+ goto out;
+
+ while (1) {
+ u32 *p;
+ br = ntfs_attr_pread(na, total, 65536, buf);
+ if (br <= 0)
+ break;
+ total += br;
+ p = (u32 *)buf + br / 4 - 1;
+ for (; (u8 *)p >= buf; p--) {
+ nr_free += lut[ *p & 255] +
+ lut[(*p >> 8) & 255] +
+ lut[(*p >> 16) & 255] +
+ lut[(*p >> 24) ];
+ }
+ switch (br % 4) {
+ case 3: nr_free += lut[*(buf + br - 3)];
+ case 2: nr_free += lut[*(buf + br - 2)];
+ case 1: nr_free += lut[*(buf + br - 1)];
+ }
+ }
+ free(buf);
+out:
+ free(lut);
+ if (!total || br < 0)
+ return -1;
+ return nr_free;
+}