mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-12-04 15:34:28 +08:00
88451c8069
User extended attributes should only be set on files and directories, not on symlinks, sockets, devices, etc. For safety they are also forbidden on metadata files, but should be allowed on the root directory. For files based on reparse points, requests are made to the plugin to determine the type.
2789 lines
76 KiB
C
2789 lines
76 KiB
C
/**
|
|
* dir.c - Directory handling code. Originated from the Linux-NTFS project.
|
|
*
|
|
* Copyright (c) 2002-2005 Anton Altaparmakov
|
|
* Copyright (c) 2004-2005 Richard Russon
|
|
* Copyright (c) 2004-2008 Szabolcs Szakacsits
|
|
* Copyright (c) 2005-2007 Yura Pakhuchiy
|
|
* Copyright (c) 2008-2014 Jean-Pierre Andre
|
|
*
|
|
* 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_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SYSMACROS_H
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
|
|
#include "param.h"
|
|
#include "types.h"
|
|
#include "debug.h"
|
|
#include "attrib.h"
|
|
#include "inode.h"
|
|
#include "dir.h"
|
|
#include "volume.h"
|
|
#include "mft.h"
|
|
#include "index.h"
|
|
#include "ntfstime.h"
|
|
#include "lcnalloc.h"
|
|
#include "logging.h"
|
|
#include "cache.h"
|
|
#include "misc.h"
|
|
#include "security.h"
|
|
#include "reparse.h"
|
|
#include "object_id.h"
|
|
|
|
#ifdef HAVE_SETXATTR
|
|
#include <sys/xattr.h>
|
|
#endif
|
|
|
|
/*
|
|
* The little endian Unicode strings "$I30", "$SII", "$SDH", "$O"
|
|
* and "$Q" as global constants.
|
|
*/
|
|
ntfschar NTFS_INDEX_I30[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('I'),
|
|
const_cpu_to_le16('3'), const_cpu_to_le16('0'),
|
|
const_cpu_to_le16('\0') };
|
|
ntfschar NTFS_INDEX_SII[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'),
|
|
const_cpu_to_le16('I'), const_cpu_to_le16('I'),
|
|
const_cpu_to_le16('\0') };
|
|
ntfschar NTFS_INDEX_SDH[5] = { const_cpu_to_le16('$'), const_cpu_to_le16('S'),
|
|
const_cpu_to_le16('D'), const_cpu_to_le16('H'),
|
|
const_cpu_to_le16('\0') };
|
|
ntfschar NTFS_INDEX_O[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('O'),
|
|
const_cpu_to_le16('\0') };
|
|
ntfschar NTFS_INDEX_Q[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('Q'),
|
|
const_cpu_to_le16('\0') };
|
|
ntfschar NTFS_INDEX_R[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('R'),
|
|
const_cpu_to_le16('\0') };
|
|
|
|
#if CACHE_INODE_SIZE
|
|
|
|
/*
|
|
* Pathname hashing
|
|
*
|
|
* Based on first char and second char (which may be '\0')
|
|
*/
|
|
|
|
int ntfs_dir_inode_hash(const struct CACHED_GENERIC *cached)
|
|
{
|
|
const char *path;
|
|
const unsigned char *name;
|
|
|
|
path = (const char*)cached->variable;
|
|
if (!path) {
|
|
ntfs_log_error("Bad inode cache entry\n");
|
|
return (-1);
|
|
}
|
|
name = (const unsigned char*)strrchr(path,'/');
|
|
if (!name)
|
|
name = (const unsigned char*)path;
|
|
return (((name[0] << 1) + name[1] + strlen((const char*)name))
|
|
% (2*CACHE_INODE_SIZE));
|
|
}
|
|
|
|
/*
|
|
* Pathname comparing for entering/fetching from cache
|
|
*/
|
|
|
|
static int inode_cache_compare(const struct CACHED_GENERIC *cached,
|
|
const struct CACHED_GENERIC *wanted)
|
|
{
|
|
return (!cached->variable
|
|
|| strcmp(cached->variable, wanted->variable));
|
|
}
|
|
|
|
/*
|
|
* Pathname comparing for invalidating entries in cache
|
|
*
|
|
* A partial path is compared in order to invalidate all paths
|
|
* related to a renamed directory
|
|
* inode numbers are also checked, as deleting a long name may
|
|
* imply deleting a short name and conversely
|
|
*
|
|
* Only use associated with a CACHE_NOHASH flag
|
|
*/
|
|
|
|
static int inode_cache_inv_compare(const struct CACHED_GENERIC *cached,
|
|
const struct CACHED_GENERIC *wanted)
|
|
{
|
|
int len;
|
|
BOOL different;
|
|
const struct CACHED_INODE *w;
|
|
const struct CACHED_INODE *c;
|
|
|
|
w = (const struct CACHED_INODE*)wanted;
|
|
c = (const struct CACHED_INODE*)cached;
|
|
if (w->pathname) {
|
|
len = strlen(w->pathname);
|
|
different = !cached->variable
|
|
|| ((w->inum != MREF(c->inum))
|
|
&& (strncmp(c->pathname, w->pathname, len)
|
|
|| ((c->pathname[len] != '\0')
|
|
&& (c->pathname[len] != '/'))));
|
|
} else
|
|
different = !c->pathname
|
|
|| (w->inum != MREF(c->inum));
|
|
return (different);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if CACHE_LOOKUP_SIZE
|
|
|
|
/*
|
|
* File name comparing for entering/fetching from lookup cache
|
|
*/
|
|
|
|
static int lookup_cache_compare(const struct CACHED_GENERIC *cached,
|
|
const struct CACHED_GENERIC *wanted)
|
|
{
|
|
const struct CACHED_LOOKUP *c = (const struct CACHED_LOOKUP*) cached;
|
|
const struct CACHED_LOOKUP *w = (const struct CACHED_LOOKUP*) wanted;
|
|
return (!c->name
|
|
|| (c->parent != w->parent)
|
|
|| (c->namesize != w->namesize)
|
|
|| memcmp(c->name, w->name, c->namesize));
|
|
}
|
|
|
|
/*
|
|
* Inode number comparing for invalidating lookup cache
|
|
*
|
|
* All entries with designated inode number are invalidated
|
|
*
|
|
* Only use associated with a CACHE_NOHASH flag
|
|
*/
|
|
|
|
static int lookup_cache_inv_compare(const struct CACHED_GENERIC *cached,
|
|
const struct CACHED_GENERIC *wanted)
|
|
{
|
|
const struct CACHED_LOOKUP *c = (const struct CACHED_LOOKUP*) cached;
|
|
const struct CACHED_LOOKUP *w = (const struct CACHED_LOOKUP*) wanted;
|
|
return (!c->name
|
|
|| (c->parent != w->parent)
|
|
|| (MREF(c->inum) != MREF(w->inum)));
|
|
}
|
|
|
|
/*
|
|
* Lookup hashing
|
|
*
|
|
* Based on first, second and and last char
|
|
*/
|
|
|
|
int ntfs_dir_lookup_hash(const struct CACHED_GENERIC *cached)
|
|
{
|
|
const unsigned char *name;
|
|
int count;
|
|
unsigned int val;
|
|
|
|
name = (const unsigned char*)cached->variable;
|
|
count = cached->varsize;
|
|
if (!name || !count) {
|
|
ntfs_log_error("Bad lookup cache entry\n");
|
|
return (-1);
|
|
}
|
|
val = (name[0] << 2) + (name[1] << 1) + name[count - 1] + count;
|
|
return (val % (2*CACHE_LOOKUP_SIZE));
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* ntfs_inode_lookup_by_name - find an inode in a directory given its name
|
|
* @dir_ni: ntfs inode of the directory in which to search for the name
|
|
* @uname: Unicode name for which to search in the directory
|
|
* @uname_len: length of the name @uname in Unicode characters
|
|
*
|
|
* Look for an inode with name @uname in the directory with inode @dir_ni.
|
|
* ntfs_inode_lookup_by_name() walks the contents of the directory looking for
|
|
* the Unicode name. If the name is found in the directory, the corresponding
|
|
* inode number (>= 0) is returned as a mft reference in cpu format, i.e. it
|
|
* is a 64-bit number containing the sequence number.
|
|
*
|
|
* On error, return -1 with errno set to the error code. If the inode is is not
|
|
* found errno is ENOENT.
|
|
*
|
|
* Note, @uname_len does not include the (optional) terminating NULL character.
|
|
*
|
|
* Note, we look for a case sensitive match first but we also look for a case
|
|
* insensitive match at the same time. If we find a case insensitive match, we
|
|
* save that for the case that we don't find an exact match, where we return
|
|
* the mft reference of the case insensitive match.
|
|
*
|
|
* If the volume is mounted with the case sensitive flag set, then we only
|
|
* allow exact matches.
|
|
*/
|
|
u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni,
|
|
const ntfschar *uname, const int uname_len)
|
|
{
|
|
VCN vcn;
|
|
u64 mref = 0;
|
|
s64 br;
|
|
ntfs_volume *vol = dir_ni->vol;
|
|
ntfs_attr_search_ctx *ctx;
|
|
INDEX_ROOT *ir;
|
|
INDEX_ENTRY *ie;
|
|
INDEX_ALLOCATION *ia;
|
|
IGNORE_CASE_BOOL case_sensitivity;
|
|
u8 *index_end;
|
|
ntfs_attr *ia_na;
|
|
int eo, rc;
|
|
u32 index_block_size;
|
|
u8 index_vcn_size_bits;
|
|
|
|
ntfs_log_trace("Entering\n");
|
|
|
|
if (!dir_ni || !dir_ni->mrec || !uname || uname_len <= 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
ctx = ntfs_attr_get_search_ctx(dir_ni, NULL);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
/* Find the index root attribute in the mft record. */
|
|
if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, CASE_SENSITIVE, 0, NULL,
|
|
0, ctx)) {
|
|
ntfs_log_perror("Index root attribute missing in directory inode "
|
|
"%lld", (unsigned long long)dir_ni->mft_no);
|
|
goto put_err_out;
|
|
}
|
|
case_sensitivity = (NVolCaseSensitive(vol) ? CASE_SENSITIVE : IGNORE_CASE);
|
|
/* Get to the index root value. */
|
|
ir = (INDEX_ROOT*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
index_block_size = le32_to_cpu(ir->index_block_size);
|
|
if (index_block_size < NTFS_BLOCK_SIZE ||
|
|
index_block_size & (index_block_size - 1)) {
|
|
ntfs_log_error("Index block size %u is invalid.\n",
|
|
(unsigned)index_block_size);
|
|
goto put_err_out;
|
|
}
|
|
index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
|
|
/* The first index entry. */
|
|
ie = (INDEX_ENTRY*)((u8*)&ir->index +
|
|
le32_to_cpu(ir->index.entries_offset));
|
|
/*
|
|
* Loop until we exceed valid memory (corruption case) or until we
|
|
* reach the last entry.
|
|
*/
|
|
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
|
|
/* Bounds checks. */
|
|
if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie +
|
|
sizeof(INDEX_ENTRY_HEADER) > index_end ||
|
|
(u8*)ie + le16_to_cpu(ie->key_length) >
|
|
index_end) {
|
|
ntfs_log_error("Index entry out of bounds in inode %lld"
|
|
"\n", (unsigned long long)dir_ni->mft_no);
|
|
goto put_err_out;
|
|
}
|
|
/*
|
|
* The last entry cannot contain a name. It can however contain
|
|
* a pointer to a child node in the B+tree so we just break out.
|
|
*/
|
|
if (ie->ie_flags & INDEX_ENTRY_END)
|
|
break;
|
|
|
|
if (!le16_to_cpu(ie->length)) {
|
|
ntfs_log_error("Zero length index entry in inode %lld"
|
|
"\n", (unsigned long long)dir_ni->mft_no);
|
|
goto put_err_out;
|
|
}
|
|
/*
|
|
* Not a perfect match, need to do full blown collation so we
|
|
* know which way in the B+tree we have to go.
|
|
*/
|
|
rc = ntfs_names_full_collate(uname, uname_len,
|
|
(ntfschar*)&ie->key.file_name.file_name,
|
|
ie->key.file_name.file_name_length,
|
|
case_sensitivity, vol->upcase, vol->upcase_len);
|
|
/*
|
|
* If uname collates before the name of the current entry, there
|
|
* is definitely no such name in this index but we might need to
|
|
* descend into the B+tree so we just break out of the loop.
|
|
*/
|
|
if (rc == -1)
|
|
break;
|
|
/* The names are not equal, continue the search. */
|
|
if (rc)
|
|
continue;
|
|
/*
|
|
* Perfect match, this will never happen as the
|
|
* ntfs_are_names_equal() call will have gotten a match but we
|
|
* still treat it correctly.
|
|
*/
|
|
mref = le64_to_cpu(ie->indexed_file);
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
return mref;
|
|
}
|
|
/*
|
|
* We have finished with this index without success. Check for the
|
|
* presence of a child node and if not present return error code
|
|
* ENOENT, unless we have got the mft reference of a matching name
|
|
* cached in mref in which case return mref.
|
|
*/
|
|
if (!(ie->ie_flags & INDEX_ENTRY_NODE)) {
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
if (mref)
|
|
return mref;
|
|
ntfs_log_debug("Entry not found - between root entries.\n");
|
|
errno = ENOENT;
|
|
return -1;
|
|
} /* Child node present, descend into it. */
|
|
|
|
/* Open the index allocation attribute. */
|
|
ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
|
|
if (!ia_na) {
|
|
ntfs_log_perror("Failed to open index allocation (inode %lld)",
|
|
(unsigned long long)dir_ni->mft_no);
|
|
goto put_err_out;
|
|
}
|
|
|
|
/* Allocate a buffer for the current index block. */
|
|
ia = ntfs_malloc(index_block_size);
|
|
if (!ia) {
|
|
ntfs_attr_close(ia_na);
|
|
goto put_err_out;
|
|
}
|
|
|
|
/* Determine the size of a vcn in the directory index. */
|
|
if (vol->cluster_size <= index_block_size) {
|
|
index_vcn_size_bits = vol->cluster_size_bits;
|
|
} else {
|
|
index_vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
|
|
}
|
|
|
|
/* Get the starting vcn of the index_block holding the child node. */
|
|
vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8));
|
|
|
|
descend_into_child_node:
|
|
|
|
/* Read the index block starting at vcn. */
|
|
br = ntfs_attr_mst_pread(ia_na, vcn << index_vcn_size_bits, 1,
|
|
index_block_size, ia);
|
|
if (br != 1) {
|
|
if (br != -1)
|
|
errno = EIO;
|
|
ntfs_log_perror("Failed to read vcn 0x%llx",
|
|
(unsigned long long)vcn);
|
|
goto close_err_out;
|
|
}
|
|
|
|
if (sle64_to_cpu(ia->index_block_vcn) != vcn) {
|
|
ntfs_log_error("Actual VCN (0x%llx) of index buffer is different "
|
|
"from expected VCN (0x%llx).\n",
|
|
(long long)sle64_to_cpu(ia->index_block_vcn),
|
|
(long long)vcn);
|
|
errno = EIO;
|
|
goto close_err_out;
|
|
}
|
|
if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) {
|
|
ntfs_log_error("Index buffer (VCN 0x%llx) of directory inode 0x%llx "
|
|
"has a size (%u) differing from the directory "
|
|
"specified size (%u).\n", (long long)vcn,
|
|
(unsigned long long)dir_ni->mft_no,
|
|
(unsigned) le32_to_cpu(ia->index.allocated_size) + 0x18,
|
|
(unsigned)index_block_size);
|
|
errno = EIO;
|
|
goto close_err_out;
|
|
}
|
|
index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);
|
|
if (index_end > (u8*)ia + index_block_size) {
|
|
ntfs_log_error("Size of index buffer (VCN 0x%llx) of directory inode "
|
|
"0x%llx exceeds maximum size.\n",
|
|
(long long)vcn, (unsigned long long)dir_ni->mft_no);
|
|
errno = EIO;
|
|
goto close_err_out;
|
|
}
|
|
|
|
/* The first index entry. */
|
|
ie = (INDEX_ENTRY*)((u8*)&ia->index +
|
|
le32_to_cpu(ia->index.entries_offset));
|
|
/*
|
|
* Iterate similar to above big loop but applied to index buffer, thus
|
|
* loop until we exceed valid memory (corruption case) or until we
|
|
* reach the last entry.
|
|
*/
|
|
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
|
|
/* Bounds check. */
|
|
if ((u8*)ie < (u8*)ia || (u8*)ie +
|
|
sizeof(INDEX_ENTRY_HEADER) > index_end ||
|
|
(u8*)ie + le16_to_cpu(ie->key_length) >
|
|
index_end) {
|
|
ntfs_log_error("Index entry out of bounds in directory "
|
|
"inode %lld.\n",
|
|
(unsigned long long)dir_ni->mft_no);
|
|
errno = EIO;
|
|
goto close_err_out;
|
|
}
|
|
/*
|
|
* The last entry cannot contain a name. It can however contain
|
|
* a pointer to a child node in the B+tree so we just break out.
|
|
*/
|
|
if (ie->ie_flags & INDEX_ENTRY_END)
|
|
break;
|
|
|
|
if (!le16_to_cpu(ie->length)) {
|
|
errno = EIO;
|
|
ntfs_log_error("Zero length index entry in inode %lld"
|
|
"\n", (unsigned long long)dir_ni->mft_no);
|
|
goto close_err_out;
|
|
}
|
|
/*
|
|
* Not a perfect match, need to do full blown collation so we
|
|
* know which way in the B+tree we have to go.
|
|
*/
|
|
rc = ntfs_names_full_collate(uname, uname_len,
|
|
(ntfschar*)&ie->key.file_name.file_name,
|
|
ie->key.file_name.file_name_length,
|
|
case_sensitivity, vol->upcase, vol->upcase_len);
|
|
/*
|
|
* If uname collates before the name of the current entry, there
|
|
* is definitely no such name in this index but we might need to
|
|
* descend into the B+tree so we just break out of the loop.
|
|
*/
|
|
if (rc == -1)
|
|
break;
|
|
/* The names are not equal, continue the search. */
|
|
if (rc)
|
|
continue;
|
|
mref = le64_to_cpu(ie->indexed_file);
|
|
free(ia);
|
|
ntfs_attr_close(ia_na);
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
return mref;
|
|
}
|
|
/*
|
|
* We have finished with this index buffer without success. Check for
|
|
* the presence of a child node.
|
|
*/
|
|
if (ie->ie_flags & INDEX_ENTRY_NODE) {
|
|
if ((ia->index.ih_flags & NODE_MASK) == LEAF_NODE) {
|
|
ntfs_log_error("Index entry with child node found in a leaf "
|
|
"node in directory inode %lld.\n",
|
|
(unsigned long long)dir_ni->mft_no);
|
|
errno = EIO;
|
|
goto close_err_out;
|
|
}
|
|
/* Child node present, descend into it. */
|
|
vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8));
|
|
if (vcn >= 0)
|
|
goto descend_into_child_node;
|
|
ntfs_log_error("Negative child node vcn in directory inode "
|
|
"0x%llx.\n", (unsigned long long)dir_ni->mft_no);
|
|
errno = EIO;
|
|
goto close_err_out;
|
|
}
|
|
free(ia);
|
|
ntfs_attr_close(ia_na);
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
/*
|
|
* No child node present, return error code ENOENT, unless we have got
|
|
* the mft reference of a matching name cached in mref in which case
|
|
* return mref.
|
|
*/
|
|
if (mref)
|
|
return mref;
|
|
ntfs_log_debug("Entry not found.\n");
|
|
errno = ENOENT;
|
|
return -1;
|
|
put_err_out:
|
|
eo = EIO;
|
|
ntfs_log_debug("Corrupt directory. Aborting lookup.\n");
|
|
eo_put_err_out:
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
errno = eo;
|
|
return -1;
|
|
close_err_out:
|
|
eo = errno;
|
|
free(ia);
|
|
ntfs_attr_close(ia_na);
|
|
goto eo_put_err_out;
|
|
}
|
|
|
|
/*
|
|
* Lookup a file in a directory from its UTF-8 name
|
|
*
|
|
* The name is first fetched from cache if one is defined
|
|
*
|
|
* Returns the inode number
|
|
* or -1 if not possible (errno tells why)
|
|
*/
|
|
|
|
u64 ntfs_inode_lookup_by_mbsname(ntfs_inode *dir_ni, const char *name)
|
|
{
|
|
int uname_len;
|
|
ntfschar *uname = (ntfschar*)NULL;
|
|
u64 inum;
|
|
char *cached_name;
|
|
const char *const_name;
|
|
|
|
if (!NVolCaseSensitive(dir_ni->vol)) {
|
|
cached_name = ntfs_uppercase_mbs(name,
|
|
dir_ni->vol->upcase, dir_ni->vol->upcase_len);
|
|
const_name = cached_name;
|
|
} else {
|
|
cached_name = (char*)NULL;
|
|
const_name = name;
|
|
}
|
|
if (const_name) {
|
|
#if CACHE_LOOKUP_SIZE
|
|
|
|
/*
|
|
* fetch inode from cache
|
|
*/
|
|
|
|
if (dir_ni->vol->lookup_cache) {
|
|
struct CACHED_LOOKUP item;
|
|
struct CACHED_LOOKUP *cached;
|
|
|
|
item.name = const_name;
|
|
item.namesize = strlen(const_name) + 1;
|
|
item.parent = dir_ni->mft_no;
|
|
cached = (struct CACHED_LOOKUP*)ntfs_fetch_cache(
|
|
dir_ni->vol->lookup_cache,
|
|
GENERIC(&item), lookup_cache_compare);
|
|
if (cached) {
|
|
inum = cached->inum;
|
|
if (inum == (u64)-1)
|
|
errno = ENOENT;
|
|
} else {
|
|
/* Generate unicode name. */
|
|
uname_len = ntfs_mbstoucs(name, &uname);
|
|
if (uname_len >= 0) {
|
|
inum = ntfs_inode_lookup_by_name(dir_ni,
|
|
uname, uname_len);
|
|
item.inum = inum;
|
|
/* enter into cache, even if not found */
|
|
ntfs_enter_cache(dir_ni->vol->lookup_cache,
|
|
GENERIC(&item),
|
|
lookup_cache_compare);
|
|
free(uname);
|
|
} else
|
|
inum = (s64)-1;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
/* Generate unicode name. */
|
|
uname_len = ntfs_mbstoucs(cached_name, &uname);
|
|
if (uname_len >= 0)
|
|
inum = ntfs_inode_lookup_by_name(dir_ni,
|
|
uname, uname_len);
|
|
else
|
|
inum = (s64)-1;
|
|
}
|
|
if (cached_name)
|
|
free(cached_name);
|
|
} else
|
|
inum = (s64)-1;
|
|
return (inum);
|
|
}
|
|
|
|
/*
|
|
* Update a cache lookup record when a name has been defined
|
|
*
|
|
* The UTF-8 name is required
|
|
*/
|
|
|
|
void ntfs_inode_update_mbsname(ntfs_inode *dir_ni, const char *name, u64 inum)
|
|
{
|
|
#if CACHE_LOOKUP_SIZE
|
|
struct CACHED_LOOKUP item;
|
|
struct CACHED_LOOKUP *cached;
|
|
char *cached_name;
|
|
|
|
if (dir_ni->vol->lookup_cache) {
|
|
if (!NVolCaseSensitive(dir_ni->vol)) {
|
|
cached_name = ntfs_uppercase_mbs(name,
|
|
dir_ni->vol->upcase, dir_ni->vol->upcase_len);
|
|
item.name = cached_name;
|
|
} else {
|
|
cached_name = (char*)NULL;
|
|
item.name = name;
|
|
}
|
|
if (item.name) {
|
|
item.namesize = strlen(item.name) + 1;
|
|
item.parent = dir_ni->mft_no;
|
|
item.inum = inum;
|
|
cached = (struct CACHED_LOOKUP*)ntfs_enter_cache(
|
|
dir_ni->vol->lookup_cache,
|
|
GENERIC(&item), lookup_cache_compare);
|
|
if (cached)
|
|
cached->inum = inum;
|
|
if (cached_name)
|
|
free(cached_name);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* ntfs_pathname_to_inode - Find the inode which represents the given pathname
|
|
* @vol: An ntfs volume obtained from ntfs_mount
|
|
* @parent: A directory inode to begin the search (may be NULL)
|
|
* @pathname: Pathname to be located
|
|
*
|
|
* Take an ASCII pathname and find the inode that represents it. The function
|
|
* splits the path and then descends the directory tree. If @parent is NULL,
|
|
* then the root directory '.' will be used as the base for the search.
|
|
*
|
|
* Return: inode Success, the pathname was valid
|
|
* NULL Error, the pathname was invalid, or some other error occurred
|
|
*/
|
|
ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent,
|
|
const char *pathname)
|
|
{
|
|
u64 inum;
|
|
int len, err = 0;
|
|
char *p, *q;
|
|
ntfs_inode *ni;
|
|
ntfs_inode *result = NULL;
|
|
ntfschar *unicode = NULL;
|
|
char *ascii = NULL;
|
|
#if CACHE_INODE_SIZE
|
|
struct CACHED_INODE item;
|
|
struct CACHED_INODE *cached;
|
|
char *fullname;
|
|
#endif
|
|
|
|
if (!vol || !pathname) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
ntfs_log_trace("path: '%s'\n", pathname);
|
|
|
|
ascii = strdup(pathname);
|
|
if (!ascii) {
|
|
ntfs_log_error("Out of memory.\n");
|
|
err = ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
p = ascii;
|
|
/* Remove leading /'s. */
|
|
while (p && *p && *p == PATH_SEP)
|
|
p++;
|
|
#if CACHE_INODE_SIZE
|
|
fullname = p;
|
|
if (p[0] && (p[strlen(p)-1] == PATH_SEP))
|
|
ntfs_log_error("Unnormalized path %s\n",ascii);
|
|
#endif
|
|
if (parent) {
|
|
ni = parent;
|
|
} else {
|
|
#if CACHE_INODE_SIZE
|
|
/*
|
|
* fetch inode for full path from cache
|
|
*/
|
|
if (*fullname) {
|
|
item.pathname = fullname;
|
|
item.varsize = strlen(fullname) + 1;
|
|
cached = (struct CACHED_INODE*)ntfs_fetch_cache(
|
|
vol->xinode_cache, GENERIC(&item),
|
|
inode_cache_compare);
|
|
} else
|
|
cached = (struct CACHED_INODE*)NULL;
|
|
if (cached) {
|
|
/*
|
|
* return opened inode if found in cache
|
|
*/
|
|
inum = MREF(cached->inum);
|
|
ni = ntfs_inode_open(vol, inum);
|
|
if (!ni) {
|
|
ntfs_log_debug("Cannot open inode %llu: %s.\n",
|
|
(unsigned long long)inum, p);
|
|
err = EIO;
|
|
}
|
|
result = ni;
|
|
goto out;
|
|
}
|
|
#endif
|
|
ni = ntfs_inode_open(vol, FILE_root);
|
|
if (!ni) {
|
|
ntfs_log_debug("Couldn't open the inode of the root "
|
|
"directory.\n");
|
|
err = EIO;
|
|
result = (ntfs_inode*)NULL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
while (p && *p) {
|
|
/* Find the end of the first token. */
|
|
q = strchr(p, PATH_SEP);
|
|
if (q != NULL) {
|
|
*q = '\0';
|
|
}
|
|
#if CACHE_INODE_SIZE
|
|
/*
|
|
* fetch inode for partial path from cache
|
|
*/
|
|
cached = (struct CACHED_INODE*)NULL;
|
|
if (!parent) {
|
|
item.pathname = fullname;
|
|
item.varsize = strlen(fullname) + 1;
|
|
cached = (struct CACHED_INODE*)ntfs_fetch_cache(
|
|
vol->xinode_cache, GENERIC(&item),
|
|
inode_cache_compare);
|
|
if (cached) {
|
|
inum = cached->inum;
|
|
}
|
|
}
|
|
/*
|
|
* if not in cache, translate, search, then
|
|
* insert into cache if found
|
|
*/
|
|
if (!cached) {
|
|
len = ntfs_mbstoucs(p, &unicode);
|
|
if (len < 0) {
|
|
ntfs_log_perror("Could not convert filename to Unicode:"
|
|
" '%s'", p);
|
|
err = errno;
|
|
goto close;
|
|
} else if (len > NTFS_MAX_NAME_LEN) {
|
|
err = ENAMETOOLONG;
|
|
goto close;
|
|
}
|
|
inum = ntfs_inode_lookup_by_name(ni, unicode, len);
|
|
if (!parent && (inum != (u64) -1)) {
|
|
item.inum = inum;
|
|
ntfs_enter_cache(vol->xinode_cache,
|
|
GENERIC(&item),
|
|
inode_cache_compare);
|
|
}
|
|
}
|
|
#else
|
|
len = ntfs_mbstoucs(p, &unicode);
|
|
if (len < 0) {
|
|
ntfs_log_perror("Could not convert filename to Unicode:"
|
|
" '%s'", p);
|
|
err = errno;
|
|
goto close;
|
|
} else if (len > NTFS_MAX_NAME_LEN) {
|
|
err = ENAMETOOLONG;
|
|
goto close;
|
|
}
|
|
inum = ntfs_inode_lookup_by_name(ni, unicode, len);
|
|
#endif
|
|
if (inum == (u64) -1) {
|
|
ntfs_log_debug("Couldn't find name '%s' in pathname "
|
|
"'%s'.\n", p, pathname);
|
|
err = ENOENT;
|
|
goto close;
|
|
}
|
|
|
|
if (ni != parent)
|
|
if (ntfs_inode_close(ni)) {
|
|
err = errno;
|
|
goto out;
|
|
}
|
|
|
|
inum = MREF(inum);
|
|
ni = ntfs_inode_open(vol, inum);
|
|
if (!ni) {
|
|
ntfs_log_debug("Cannot open inode %llu: %s.\n",
|
|
(unsigned long long)inum, p);
|
|
err = EIO;
|
|
goto close;
|
|
}
|
|
|
|
free(unicode);
|
|
unicode = NULL;
|
|
|
|
if (q) *q++ = PATH_SEP; /* JPA */
|
|
p = q;
|
|
while (p && *p && *p == PATH_SEP)
|
|
p++;
|
|
}
|
|
|
|
result = ni;
|
|
ni = NULL;
|
|
close:
|
|
if (ni && (ni != parent))
|
|
if (ntfs_inode_close(ni) && !err)
|
|
err = errno;
|
|
out:
|
|
free(ascii);
|
|
free(unicode);
|
|
if (err)
|
|
errno = err;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* The little endian Unicode string ".." for ntfs_readdir().
|
|
*/
|
|
static const ntfschar dotdot[3] = { const_cpu_to_le16('.'),
|
|
const_cpu_to_le16('.'),
|
|
const_cpu_to_le16('\0') };
|
|
|
|
/*
|
|
* union index_union -
|
|
* More helpers for ntfs_readdir().
|
|
*/
|
|
typedef union {
|
|
INDEX_ROOT *ir;
|
|
INDEX_ALLOCATION *ia;
|
|
} index_union __attribute__((__transparent_union__));
|
|
|
|
/**
|
|
* enum INDEX_TYPE -
|
|
* More helpers for ntfs_readdir().
|
|
*/
|
|
typedef enum {
|
|
INDEX_TYPE_ROOT, /* index root */
|
|
INDEX_TYPE_ALLOCATION, /* index allocation */
|
|
} INDEX_TYPE;
|
|
|
|
/*
|
|
* Decode Interix file types
|
|
*
|
|
* Non-Interix types are returned as plain files, because a
|
|
* Windows user may force patterns very similar to Interix,
|
|
* and most metadata files have such similar patters.
|
|
*/
|
|
|
|
u32 ntfs_interix_types(ntfs_inode *ni)
|
|
{
|
|
ntfs_attr *na;
|
|
u32 dt_type;
|
|
le64 magic;
|
|
|
|
dt_type = NTFS_DT_UNKNOWN;
|
|
na = ntfs_attr_open(ni, AT_DATA, NULL, 0);
|
|
if (na) {
|
|
/*
|
|
* Unrecognized patterns (eg HID + SYST for metadata)
|
|
* are plain files or directories
|
|
*/
|
|
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
|
|
dt_type = NTFS_DT_DIR;
|
|
else
|
|
dt_type = NTFS_DT_REG;
|
|
if (na->data_size <= 1) {
|
|
if (!(ni->flags & FILE_ATTR_HIDDEN))
|
|
dt_type = (na->data_size ?
|
|
NTFS_DT_SOCK : NTFS_DT_FIFO);
|
|
} else {
|
|
if ((na->data_size >= (s64)sizeof(magic))
|
|
&& (ntfs_attr_pread(na, 0, sizeof(magic), &magic)
|
|
== sizeof(magic))) {
|
|
if (magic == INTX_SYMBOLIC_LINK)
|
|
dt_type = NTFS_DT_LNK;
|
|
else if (magic == INTX_BLOCK_DEVICE)
|
|
dt_type = NTFS_DT_BLK;
|
|
else if (magic == INTX_CHARACTER_DEVICE)
|
|
dt_type = NTFS_DT_CHR;
|
|
}
|
|
}
|
|
ntfs_attr_close(na);
|
|
}
|
|
return (dt_type);
|
|
}
|
|
|
|
/*
|
|
* Decode file types
|
|
*
|
|
* Better only use for Interix types and junctions,
|
|
* unneeded complexity when used for plain files or directories
|
|
*
|
|
* Error cases are logged and returned as unknown.
|
|
*/
|
|
|
|
static u32 ntfs_dir_entry_type(ntfs_inode *dir_ni, MFT_REF mref,
|
|
FILE_ATTR_FLAGS attributes)
|
|
{
|
|
ntfs_inode *ni;
|
|
u32 dt_type;
|
|
|
|
dt_type = NTFS_DT_UNKNOWN;
|
|
ni = ntfs_inode_open(dir_ni->vol, mref);
|
|
if (ni) {
|
|
if ((attributes & FILE_ATTR_REPARSE_POINT)
|
|
&& ntfs_possible_symlink(ni))
|
|
dt_type = NTFS_DT_LNK;
|
|
else
|
|
if ((attributes & FILE_ATTR_SYSTEM)
|
|
&& !(attributes & FILE_ATTR_I30_INDEX_PRESENT))
|
|
dt_type = ntfs_interix_types(ni);
|
|
else
|
|
dt_type = (attributes
|
|
& FILE_ATTR_I30_INDEX_PRESENT
|
|
? NTFS_DT_DIR : NTFS_DT_REG);
|
|
if (ntfs_inode_close(ni)) {
|
|
/* anything special worth doing ? */
|
|
ntfs_log_error("Failed to close inode %lld\n",
|
|
(long long)MREF(mref));
|
|
}
|
|
}
|
|
if (dt_type == NTFS_DT_UNKNOWN)
|
|
ntfs_log_error("Could not decode the type of inode %lld\n",
|
|
(long long)MREF(mref));
|
|
return (dt_type);
|
|
}
|
|
|
|
/**
|
|
* ntfs_filldir - ntfs specific filldir method
|
|
* @dir_ni: ntfs inode of current directory
|
|
* @pos: current position in directory
|
|
* @ivcn_bits: log(2) of index vcn size
|
|
* @index_type: specifies whether @iu is an index root or an index allocation
|
|
* @iu: index root or index block to which @ie belongs
|
|
* @ie: current index entry
|
|
* @dirent: context for filldir callback supplied by the caller
|
|
* @filldir: filldir callback supplied by the caller
|
|
*
|
|
* Pass information specifying the current directory entry @ie to the @filldir
|
|
* callback.
|
|
*/
|
|
static int ntfs_filldir(ntfs_inode *dir_ni, s64 *pos, u8 ivcn_bits,
|
|
const INDEX_TYPE index_type, index_union iu, INDEX_ENTRY *ie,
|
|
void *dirent, ntfs_filldir_t filldir)
|
|
{
|
|
FILE_NAME_ATTR *fn = &ie->key.file_name;
|
|
unsigned dt_type;
|
|
BOOL metadata;
|
|
ntfschar *loname;
|
|
int res;
|
|
MFT_REF mref;
|
|
|
|
ntfs_log_trace("Entering.\n");
|
|
|
|
/* Advance the position even if going to skip the entry. */
|
|
if (index_type == INDEX_TYPE_ALLOCATION)
|
|
*pos = (u8*)ie - (u8*)iu.ia + (sle64_to_cpu(
|
|
iu.ia->index_block_vcn) << ivcn_bits) +
|
|
dir_ni->vol->mft_record_size;
|
|
else /* if (index_type == INDEX_TYPE_ROOT) */
|
|
*pos = (u8*)ie - (u8*)iu.ir;
|
|
mref = le64_to_cpu(ie->indexed_file);
|
|
metadata = (MREF(mref) != FILE_root) && (MREF(mref) < FILE_first_user);
|
|
/* Skip root directory self reference entry. */
|
|
if (MREF_LE(ie->indexed_file) == FILE_root)
|
|
return 0;
|
|
if ((ie->key.file_name.file_attributes
|
|
& (FILE_ATTR_REPARSE_POINT | FILE_ATTR_SYSTEM))
|
|
&& !metadata)
|
|
dt_type = ntfs_dir_entry_type(dir_ni, mref,
|
|
ie->key.file_name.file_attributes);
|
|
else if (ie->key.file_name.file_attributes
|
|
& FILE_ATTR_I30_INDEX_PRESENT)
|
|
dt_type = NTFS_DT_DIR;
|
|
else
|
|
dt_type = NTFS_DT_REG;
|
|
|
|
/* return metadata files and hidden files if requested */
|
|
if ((!metadata && (NVolShowHidFiles(dir_ni->vol)
|
|
|| !(fn->file_attributes & FILE_ATTR_HIDDEN)))
|
|
|| (NVolShowSysFiles(dir_ni->vol) && (NVolShowHidFiles(dir_ni->vol)
|
|
|| metadata))) {
|
|
if (NVolCaseSensitive(dir_ni->vol)) {
|
|
res = filldir(dirent, fn->file_name,
|
|
fn->file_name_length,
|
|
fn->file_name_type, *pos,
|
|
mref, dt_type);
|
|
} else {
|
|
loname = (ntfschar*)ntfs_malloc(2*fn->file_name_length);
|
|
if (loname) {
|
|
memcpy(loname, fn->file_name,
|
|
2*fn->file_name_length);
|
|
ntfs_name_locase(loname, fn->file_name_length,
|
|
dir_ni->vol->locase,
|
|
dir_ni->vol->upcase_len);
|
|
res = filldir(dirent, loname,
|
|
fn->file_name_length,
|
|
fn->file_name_type, *pos,
|
|
mref, dt_type);
|
|
free(loname);
|
|
} else
|
|
res = -1;
|
|
}
|
|
} else
|
|
res = 0;
|
|
return (res);
|
|
}
|
|
|
|
/**
|
|
* ntfs_mft_get_parent_ref - find mft reference of parent directory of an inode
|
|
* @ni: ntfs inode whose parent directory to find
|
|
*
|
|
* Find the parent directory of the ntfs inode @ni. To do this, find the first
|
|
* file name attribute in the mft record of @ni and return the parent mft
|
|
* reference from that.
|
|
*
|
|
* Note this only makes sense for directories, since files can be hard linked
|
|
* from multiple directories and there is no way for us to tell which one is
|
|
* being looked for.
|
|
*
|
|
* Technically directories can have hard links, too, but we consider that as
|
|
* illegal as Linux/UNIX do not support directory hard links.
|
|
*
|
|
* Return the mft reference of the parent directory on success or -1 on error
|
|
* with errno set to the error code.
|
|
*/
|
|
static MFT_REF ntfs_mft_get_parent_ref(ntfs_inode *ni)
|
|
{
|
|
MFT_REF mref;
|
|
ntfs_attr_search_ctx *ctx;
|
|
FILE_NAME_ATTR *fn;
|
|
int eo;
|
|
|
|
ntfs_log_trace("Entering.\n");
|
|
|
|
if (!ni) {
|
|
errno = EINVAL;
|
|
return ERR_MREF(-1);
|
|
}
|
|
|
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
|
if (!ctx)
|
|
return ERR_MREF(-1);
|
|
if (ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) {
|
|
ntfs_log_error("No file name found in inode %lld\n",
|
|
(unsigned long long)ni->mft_no);
|
|
goto err_out;
|
|
}
|
|
if (ctx->attr->non_resident) {
|
|
ntfs_log_error("File name attribute must be resident (inode "
|
|
"%lld)\n", (unsigned long long)ni->mft_no);
|
|
goto io_err_out;
|
|
}
|
|
fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
if ((u8*)fn + le32_to_cpu(ctx->attr->value_length) >
|
|
(u8*)ctx->attr + le32_to_cpu(ctx->attr->length)) {
|
|
ntfs_log_error("Corrupt file name attribute in inode %lld.\n",
|
|
(unsigned long long)ni->mft_no);
|
|
goto io_err_out;
|
|
}
|
|
mref = le64_to_cpu(fn->parent_directory);
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
return mref;
|
|
io_err_out:
|
|
errno = EIO;
|
|
err_out:
|
|
eo = errno;
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
errno = eo;
|
|
return ERR_MREF(-1);
|
|
}
|
|
|
|
/**
|
|
* ntfs_readdir - read the contents of an ntfs directory
|
|
* @dir_ni: ntfs inode of current directory
|
|
* @pos: current position in directory
|
|
* @dirent: context for filldir callback supplied by the caller
|
|
* @filldir: filldir callback supplied by the caller
|
|
*
|
|
* Parse the index root and the index blocks that are marked in use in the
|
|
* index bitmap and hand each found directory entry to the @filldir callback
|
|
* supplied by the caller.
|
|
*
|
|
* Return 0 on success or -1 on error with errno set to the error code.
|
|
*
|
|
* Note: Index blocks are parsed in ascending vcn order, from which follows
|
|
* that the directory entries are not returned sorted.
|
|
*/
|
|
int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos,
|
|
void *dirent, ntfs_filldir_t filldir)
|
|
{
|
|
s64 i_size, br, ia_pos, bmp_pos, ia_start;
|
|
ntfs_volume *vol;
|
|
ntfs_attr *ia_na, *bmp_na = NULL;
|
|
ntfs_attr_search_ctx *ctx = NULL;
|
|
u8 *index_end, *bmp = NULL;
|
|
INDEX_ROOT *ir;
|
|
INDEX_ENTRY *ie;
|
|
INDEX_ALLOCATION *ia = NULL;
|
|
int rc, ir_pos, bmp_buf_size, bmp_buf_pos, eo;
|
|
u32 index_block_size;
|
|
u8 index_block_size_bits, index_vcn_size_bits;
|
|
|
|
ntfs_log_trace("Entering.\n");
|
|
|
|
if (!dir_ni || !pos || !filldir) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (!(dir_ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
|
|
errno = ENOTDIR;
|
|
return -1;
|
|
}
|
|
|
|
vol = dir_ni->vol;
|
|
|
|
ntfs_log_trace("Entering for inode %lld, *pos 0x%llx.\n",
|
|
(unsigned long long)dir_ni->mft_no, (long long)*pos);
|
|
|
|
/* Open the index allocation attribute. */
|
|
ia_na = ntfs_attr_open(dir_ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4);
|
|
if (!ia_na) {
|
|
if (errno != ENOENT) {
|
|
ntfs_log_perror("Failed to open index allocation attribute. "
|
|
"Directory inode %lld is corrupt or bug",
|
|
(unsigned long long)dir_ni->mft_no);
|
|
return -1;
|
|
}
|
|
i_size = 0;
|
|
} else
|
|
i_size = ia_na->data_size;
|
|
|
|
rc = 0;
|
|
|
|
/* Are we at end of dir yet? */
|
|
if (*pos >= i_size + vol->mft_record_size)
|
|
goto done;
|
|
|
|
/* Emulate . and .. for all directories. */
|
|
if (!*pos) {
|
|
rc = filldir(dirent, dotdot, 1, FILE_NAME_POSIX, *pos,
|
|
MK_MREF(dir_ni->mft_no,
|
|
le16_to_cpu(dir_ni->mrec->sequence_number)),
|
|
NTFS_DT_DIR);
|
|
if (rc)
|
|
goto err_out;
|
|
++*pos;
|
|
}
|
|
if (*pos == 1) {
|
|
MFT_REF parent_mref;
|
|
|
|
parent_mref = ntfs_mft_get_parent_ref(dir_ni);
|
|
if (parent_mref == ERR_MREF(-1)) {
|
|
ntfs_log_perror("Parent directory not found");
|
|
goto dir_err_out;
|
|
}
|
|
|
|
rc = filldir(dirent, dotdot, 2, FILE_NAME_POSIX, *pos,
|
|
parent_mref, NTFS_DT_DIR);
|
|
if (rc)
|
|
goto err_out;
|
|
++*pos;
|
|
}
|
|
|
|
ctx = ntfs_attr_get_search_ctx(dir_ni, NULL);
|
|
if (!ctx)
|
|
goto err_out;
|
|
|
|
/* Get the offset into the index root attribute. */
|
|
ir_pos = (int)*pos;
|
|
/* Find the index root attribute in the mft record. */
|
|
if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, CASE_SENSITIVE, 0, NULL,
|
|
0, ctx)) {
|
|
ntfs_log_perror("Index root attribute missing in directory inode "
|
|
"%lld", (unsigned long long)dir_ni->mft_no);
|
|
goto dir_err_out;
|
|
}
|
|
/* Get to the index root value. */
|
|
ir = (INDEX_ROOT*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
|
|
/* Determine the size of a vcn in the directory index. */
|
|
index_block_size = le32_to_cpu(ir->index_block_size);
|
|
if (index_block_size < NTFS_BLOCK_SIZE ||
|
|
index_block_size & (index_block_size - 1)) {
|
|
ntfs_log_error("Index block size %u is invalid.\n",
|
|
(unsigned)index_block_size);
|
|
goto dir_err_out;
|
|
}
|
|
index_block_size_bits = ffs(index_block_size) - 1;
|
|
if (vol->cluster_size <= index_block_size) {
|
|
index_vcn_size_bits = vol->cluster_size_bits;
|
|
} else {
|
|
index_vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
|
|
}
|
|
|
|
/* Are we jumping straight into the index allocation attribute? */
|
|
if (*pos >= vol->mft_record_size) {
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
ctx = NULL;
|
|
goto skip_index_root;
|
|
}
|
|
|
|
index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
|
|
/* The first index entry. */
|
|
ie = (INDEX_ENTRY*)((u8*)&ir->index +
|
|
le32_to_cpu(ir->index.entries_offset));
|
|
/*
|
|
* Loop until we exceed valid memory (corruption case) or until we
|
|
* reach the last entry or until filldir tells us it has had enough
|
|
* or signals an error (both covered by the rc test).
|
|
*/
|
|
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
|
|
ntfs_log_debug("In index root, offset %d.\n", (int)((u8*)ie - (u8*)ir));
|
|
/* Bounds checks. */
|
|
if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie +
|
|
sizeof(INDEX_ENTRY_HEADER) > index_end ||
|
|
(u8*)ie + le16_to_cpu(ie->key_length) >
|
|
index_end)
|
|
goto dir_err_out;
|
|
/* The last entry cannot contain a name. */
|
|
if (ie->ie_flags & INDEX_ENTRY_END)
|
|
break;
|
|
|
|
if (!le16_to_cpu(ie->length))
|
|
goto dir_err_out;
|
|
|
|
/* Skip index root entry if continuing previous readdir. */
|
|
if (ir_pos > (u8*)ie - (u8*)ir)
|
|
continue;
|
|
/*
|
|
* Submit the directory entry to ntfs_filldir(), which will
|
|
* invoke the filldir() callback as appropriate.
|
|
*/
|
|
rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits,
|
|
INDEX_TYPE_ROOT, ir, ie, dirent, filldir);
|
|
if (rc) {
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
ctx = NULL;
|
|
goto err_out;
|
|
}
|
|
}
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
ctx = NULL;
|
|
|
|
/* If there is no index allocation attribute we are finished. */
|
|
if (!ia_na)
|
|
goto EOD;
|
|
|
|
/* Advance *pos to the beginning of the index allocation. */
|
|
*pos = vol->mft_record_size;
|
|
|
|
skip_index_root:
|
|
|
|
if (!ia_na)
|
|
goto done;
|
|
|
|
/* Allocate a buffer for the current index block. */
|
|
ia = ntfs_malloc(index_block_size);
|
|
if (!ia)
|
|
goto err_out;
|
|
|
|
bmp_na = ntfs_attr_open(dir_ni, AT_BITMAP, NTFS_INDEX_I30, 4);
|
|
if (!bmp_na) {
|
|
ntfs_log_perror("Failed to open index bitmap attribute");
|
|
goto dir_err_out;
|
|
}
|
|
|
|
/* Get the offset into the index allocation attribute. */
|
|
ia_pos = *pos - vol->mft_record_size;
|
|
|
|
bmp_pos = ia_pos >> index_block_size_bits;
|
|
if (bmp_pos >> 3 >= bmp_na->data_size) {
|
|
ntfs_log_error("Current index position exceeds index bitmap "
|
|
"size.\n");
|
|
goto dir_err_out;
|
|
}
|
|
|
|
bmp_buf_size = min(bmp_na->data_size - (bmp_pos >> 3), 4096);
|
|
bmp = ntfs_malloc(bmp_buf_size);
|
|
if (!bmp)
|
|
goto err_out;
|
|
|
|
br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp);
|
|
if (br != bmp_buf_size) {
|
|
if (br != -1)
|
|
errno = EIO;
|
|
ntfs_log_perror("Failed to read from index bitmap attribute");
|
|
goto err_out;
|
|
}
|
|
|
|
bmp_buf_pos = 0;
|
|
/* If the index block is not in use find the next one that is. */
|
|
while (!(bmp[bmp_buf_pos >> 3] & (1 << (bmp_buf_pos & 7)))) {
|
|
find_next_index_buffer:
|
|
bmp_pos++;
|
|
bmp_buf_pos++;
|
|
/* If we have reached the end of the bitmap, we are done. */
|
|
if (bmp_pos >> 3 >= bmp_na->data_size)
|
|
goto EOD;
|
|
ia_pos = bmp_pos << index_block_size_bits;
|
|
if (bmp_buf_pos >> 3 < bmp_buf_size)
|
|
continue;
|
|
/* Read next chunk from the index bitmap. */
|
|
bmp_buf_pos = 0;
|
|
if ((bmp_pos >> 3) + bmp_buf_size > bmp_na->data_size)
|
|
bmp_buf_size = bmp_na->data_size - (bmp_pos >> 3);
|
|
br = ntfs_attr_pread(bmp_na, bmp_pos >> 3, bmp_buf_size, bmp);
|
|
if (br != bmp_buf_size) {
|
|
if (br != -1)
|
|
errno = EIO;
|
|
ntfs_log_perror("Failed to read from index bitmap attribute");
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
ntfs_log_debug("Handling index block 0x%llx.\n", (long long)bmp_pos);
|
|
|
|
/* Read the index block starting at bmp_pos. */
|
|
br = ntfs_attr_mst_pread(ia_na, bmp_pos << index_block_size_bits, 1,
|
|
index_block_size, ia);
|
|
if (br != 1) {
|
|
if (br != -1)
|
|
errno = EIO;
|
|
ntfs_log_perror("Failed to read index block");
|
|
goto err_out;
|
|
}
|
|
|
|
ia_start = ia_pos & ~(s64)(index_block_size - 1);
|
|
if (sle64_to_cpu(ia->index_block_vcn) != ia_start >>
|
|
index_vcn_size_bits) {
|
|
ntfs_log_error("Actual VCN (0x%llx) of index buffer is different "
|
|
"from expected VCN (0x%llx) in inode 0x%llx.\n",
|
|
(long long)sle64_to_cpu(ia->index_block_vcn),
|
|
(long long)ia_start >> index_vcn_size_bits,
|
|
(unsigned long long)dir_ni->mft_no);
|
|
goto dir_err_out;
|
|
}
|
|
if (le32_to_cpu(ia->index.allocated_size) + 0x18 != index_block_size) {
|
|
ntfs_log_error("Index buffer (VCN 0x%llx) of directory inode %lld "
|
|
"has a size (%u) differing from the directory "
|
|
"specified size (%u).\n", (long long)ia_start >>
|
|
index_vcn_size_bits,
|
|
(unsigned long long)dir_ni->mft_no,
|
|
(unsigned) le32_to_cpu(ia->index.allocated_size)
|
|
+ 0x18, (unsigned)index_block_size);
|
|
goto dir_err_out;
|
|
}
|
|
index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);
|
|
if (index_end > (u8*)ia + index_block_size) {
|
|
ntfs_log_error("Size of index buffer (VCN 0x%llx) of directory inode "
|
|
"%lld exceeds maximum size.\n",
|
|
(long long)ia_start >> index_vcn_size_bits,
|
|
(unsigned long long)dir_ni->mft_no);
|
|
goto dir_err_out;
|
|
}
|
|
/* The first index entry. */
|
|
ie = (INDEX_ENTRY*)((u8*)&ia->index +
|
|
le32_to_cpu(ia->index.entries_offset));
|
|
/*
|
|
* Loop until we exceed valid memory (corruption case) or until we
|
|
* reach the last entry or until ntfs_filldir tells us it has had
|
|
* enough or signals an error (both covered by the rc test).
|
|
*/
|
|
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
|
|
ntfs_log_debug("In index allocation, offset 0x%llx.\n",
|
|
(long long)ia_start + ((u8*)ie - (u8*)ia));
|
|
/* Bounds checks. */
|
|
if ((u8*)ie < (u8*)ia || (u8*)ie +
|
|
sizeof(INDEX_ENTRY_HEADER) > index_end ||
|
|
(u8*)ie + le16_to_cpu(ie->key_length) >
|
|
index_end) {
|
|
ntfs_log_error("Index entry out of bounds in directory inode "
|
|
"%lld.\n", (unsigned long long)dir_ni->mft_no);
|
|
goto dir_err_out;
|
|
}
|
|
/* The last entry cannot contain a name. */
|
|
if (ie->ie_flags & INDEX_ENTRY_END)
|
|
break;
|
|
|
|
if (!le16_to_cpu(ie->length))
|
|
goto dir_err_out;
|
|
|
|
/* Skip index entry if continuing previous readdir. */
|
|
if (ia_pos - ia_start > (u8*)ie - (u8*)ia)
|
|
continue;
|
|
/*
|
|
* Submit the directory entry to ntfs_filldir(), which will
|
|
* invoke the filldir() callback as appropriate.
|
|
*/
|
|
rc = ntfs_filldir(dir_ni, pos, index_vcn_size_bits,
|
|
INDEX_TYPE_ALLOCATION, ia, ie, dirent, filldir);
|
|
if (rc)
|
|
goto err_out;
|
|
}
|
|
goto find_next_index_buffer;
|
|
EOD:
|
|
/* We are finished, set *pos to EOD. */
|
|
*pos = i_size + vol->mft_record_size;
|
|
done:
|
|
free(ia);
|
|
free(bmp);
|
|
if (bmp_na)
|
|
ntfs_attr_close(bmp_na);
|
|
if (ia_na)
|
|
ntfs_attr_close(ia_na);
|
|
ntfs_log_debug("EOD, *pos 0x%llx, returning 0.\n", (long long)*pos);
|
|
return 0;
|
|
dir_err_out:
|
|
errno = EIO;
|
|
err_out:
|
|
eo = errno;
|
|
ntfs_log_trace("failed.\n");
|
|
if (ctx)
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
free(ia);
|
|
free(bmp);
|
|
if (bmp_na)
|
|
ntfs_attr_close(bmp_na);
|
|
if (ia_na)
|
|
ntfs_attr_close(ia_na);
|
|
errno = eo;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* __ntfs_create - create object on ntfs volume
|
|
* @dir_ni: ntfs inode for directory in which create new object
|
|
* @securid: id of inheritable security descriptor, 0 if none
|
|
* @name: unicode name of new object
|
|
* @name_len: length of the name in unicode characters
|
|
* @type: type of the object to create
|
|
* @dev: major and minor device numbers (obtained from makedev())
|
|
* @target: target in unicode (only for symlinks)
|
|
* @target_len: length of target in unicode characters
|
|
*
|
|
* Internal, use ntfs_create{,_device,_symlink} wrappers instead.
|
|
*
|
|
* @type can be:
|
|
* S_IFREG to create regular file
|
|
* S_IFDIR to create directory
|
|
* S_IFBLK to create block device
|
|
* S_IFCHR to create character device
|
|
* S_IFLNK to create symbolic link
|
|
* S_IFIFO to create FIFO
|
|
* S_IFSOCK to create socket
|
|
* other values are invalid.
|
|
*
|
|
* @dev is used only if @type is S_IFBLK or S_IFCHR, in other cases its value
|
|
* ignored.
|
|
*
|
|
* @target and @target_len are used only if @type is S_IFLNK, in other cases
|
|
* their value ignored.
|
|
*
|
|
* Return opened ntfs inode that describes created object on success or NULL
|
|
* on error with errno set to the error code.
|
|
*/
|
|
static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid,
|
|
const ntfschar *name, u8 name_len, mode_t type, dev_t dev,
|
|
const ntfschar *target, int target_len)
|
|
{
|
|
ntfs_inode *ni;
|
|
int rollback_data = 0, rollback_sd = 0;
|
|
FILE_NAME_ATTR *fn = NULL;
|
|
STANDARD_INFORMATION *si = NULL;
|
|
int err, fn_len, si_len;
|
|
|
|
ntfs_log_trace("Entering.\n");
|
|
|
|
/* Sanity checks. */
|
|
if (!dir_ni || !name || !name_len) {
|
|
ntfs_log_error("Invalid arguments.\n");
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
|
|
errno = EOPNOTSUPP;
|
|
return NULL;
|
|
}
|
|
|
|
ni = ntfs_mft_record_alloc(dir_ni->vol, NULL);
|
|
if (!ni)
|
|
return NULL;
|
|
#if CACHE_NIDATA_SIZE
|
|
ntfs_inode_invalidate(dir_ni->vol, ni->mft_no);
|
|
#endif
|
|
/*
|
|
* Create STANDARD_INFORMATION attribute.
|
|
* JPA Depending on available inherited security descriptor,
|
|
* Write STANDARD_INFORMATION v1.2 (no inheritance) or v3
|
|
*/
|
|
if (securid)
|
|
si_len = sizeof(STANDARD_INFORMATION);
|
|
else
|
|
si_len = offsetof(STANDARD_INFORMATION, v1_end);
|
|
si = ntfs_calloc(si_len);
|
|
if (!si) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
si->creation_time = ni->creation_time;
|
|
si->last_data_change_time = ni->last_data_change_time;
|
|
si->last_mft_change_time = ni->last_mft_change_time;
|
|
si->last_access_time = ni->last_access_time;
|
|
if (securid) {
|
|
set_nino_flag(ni, v3_Extensions);
|
|
ni->owner_id = si->owner_id = const_cpu_to_le32(0);
|
|
ni->security_id = si->security_id = securid;
|
|
ni->quota_charged = si->quota_charged = const_cpu_to_le64(0);
|
|
ni->usn = si->usn = const_cpu_to_le64(0);
|
|
} else
|
|
clear_nino_flag(ni, v3_Extensions);
|
|
if (!S_ISREG(type) && !S_ISDIR(type)) {
|
|
si->file_attributes = FILE_ATTR_SYSTEM;
|
|
ni->flags = FILE_ATTR_SYSTEM;
|
|
}
|
|
ni->flags |= FILE_ATTR_ARCHIVE;
|
|
if (NVolHideDotFiles(dir_ni->vol)
|
|
&& (name_len > 1)
|
|
&& (name[0] == const_cpu_to_le16('.'))
|
|
&& (name[1] != const_cpu_to_le16('.')))
|
|
ni->flags |= FILE_ATTR_HIDDEN;
|
|
/*
|
|
* Set compression flag according to parent directory
|
|
* unless NTFS version < 3.0 or cluster size > 4K
|
|
* or compression has been disabled
|
|
*/
|
|
if ((dir_ni->flags & FILE_ATTR_COMPRESSED)
|
|
&& (dir_ni->vol->major_ver >= 3)
|
|
&& NVolCompression(dir_ni->vol)
|
|
&& (dir_ni->vol->cluster_size <= MAX_COMPRESSION_CLUSTER_SIZE)
|
|
&& (S_ISREG(type) || S_ISDIR(type)))
|
|
ni->flags |= FILE_ATTR_COMPRESSED;
|
|
/* Add STANDARD_INFORMATION to inode. */
|
|
if (ntfs_attr_add(ni, AT_STANDARD_INFORMATION, AT_UNNAMED, 0,
|
|
(u8*)si, si_len)) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to add STANDARD_INFORMATION "
|
|
"attribute.\n");
|
|
goto err_out;
|
|
}
|
|
|
|
if (!securid) {
|
|
if (ntfs_sd_add_everyone(ni)) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
}
|
|
rollback_sd = 1;
|
|
|
|
if (S_ISDIR(type)) {
|
|
INDEX_ROOT *ir = NULL;
|
|
INDEX_ENTRY *ie;
|
|
int ir_len, index_len;
|
|
|
|
/* Create INDEX_ROOT attribute. */
|
|
index_len = sizeof(INDEX_HEADER) + sizeof(INDEX_ENTRY_HEADER);
|
|
ir_len = offsetof(INDEX_ROOT, index) + index_len;
|
|
ir = ntfs_calloc(ir_len);
|
|
if (!ir) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
ir->type = AT_FILE_NAME;
|
|
ir->collation_rule = COLLATION_FILE_NAME;
|
|
ir->index_block_size = cpu_to_le32(ni->vol->indx_record_size);
|
|
if (ni->vol->cluster_size <= ni->vol->indx_record_size)
|
|
ir->clusters_per_index_block =
|
|
ni->vol->indx_record_size >>
|
|
ni->vol->cluster_size_bits;
|
|
else
|
|
ir->clusters_per_index_block =
|
|
ni->vol->indx_record_size >>
|
|
NTFS_BLOCK_SIZE_BITS;
|
|
ir->index.entries_offset = const_cpu_to_le32(sizeof(INDEX_HEADER));
|
|
ir->index.index_length = cpu_to_le32(index_len);
|
|
ir->index.allocated_size = cpu_to_le32(index_len);
|
|
ie = (INDEX_ENTRY*)((u8*)ir + sizeof(INDEX_ROOT));
|
|
ie->length = const_cpu_to_le16(sizeof(INDEX_ENTRY_HEADER));
|
|
ie->key_length = const_cpu_to_le16(0);
|
|
ie->ie_flags = INDEX_ENTRY_END;
|
|
/* Add INDEX_ROOT attribute to inode. */
|
|
if (ntfs_attr_add(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4,
|
|
(u8*)ir, ir_len)) {
|
|
err = errno;
|
|
free(ir);
|
|
ntfs_log_error("Failed to add INDEX_ROOT attribute.\n");
|
|
goto err_out;
|
|
}
|
|
free(ir);
|
|
} else {
|
|
INTX_FILE *data;
|
|
int data_len;
|
|
|
|
switch (type) {
|
|
case S_IFBLK:
|
|
case S_IFCHR:
|
|
data_len = offsetof(INTX_FILE, device_end);
|
|
data = ntfs_malloc(data_len);
|
|
if (!data) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
data->major = cpu_to_le64(major(dev));
|
|
data->minor = cpu_to_le64(minor(dev));
|
|
if (type == S_IFBLK)
|
|
data->magic = INTX_BLOCK_DEVICE;
|
|
if (type == S_IFCHR)
|
|
data->magic = INTX_CHARACTER_DEVICE;
|
|
break;
|
|
case S_IFLNK:
|
|
data_len = sizeof(INTX_FILE_TYPES) +
|
|
target_len * sizeof(ntfschar);
|
|
data = ntfs_malloc(data_len);
|
|
if (!data) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
data->magic = INTX_SYMBOLIC_LINK;
|
|
memcpy(data->target, target,
|
|
target_len * sizeof(ntfschar));
|
|
break;
|
|
case S_IFSOCK:
|
|
data = NULL;
|
|
data_len = 1;
|
|
break;
|
|
default: /* FIFO or regular file. */
|
|
data = NULL;
|
|
data_len = 0;
|
|
break;
|
|
}
|
|
/* Add DATA attribute to inode. */
|
|
if (ntfs_attr_add(ni, AT_DATA, AT_UNNAMED, 0, (u8*)data,
|
|
data_len)) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to add DATA attribute.\n");
|
|
free(data);
|
|
goto err_out;
|
|
}
|
|
rollback_data = 1;
|
|
free(data);
|
|
}
|
|
/* Create FILE_NAME attribute. */
|
|
fn_len = sizeof(FILE_NAME_ATTR) + name_len * sizeof(ntfschar);
|
|
fn = ntfs_calloc(fn_len);
|
|
if (!fn) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
fn->parent_directory = MK_LE_MREF(dir_ni->mft_no,
|
|
le16_to_cpu(dir_ni->mrec->sequence_number));
|
|
fn->file_name_length = name_len;
|
|
fn->file_name_type = FILE_NAME_POSIX;
|
|
if (S_ISDIR(type))
|
|
fn->file_attributes = FILE_ATTR_I30_INDEX_PRESENT;
|
|
if (!S_ISREG(type) && !S_ISDIR(type))
|
|
fn->file_attributes = FILE_ATTR_SYSTEM;
|
|
else
|
|
fn->file_attributes |= ni->flags & FILE_ATTR_COMPRESSED;
|
|
fn->file_attributes |= FILE_ATTR_ARCHIVE;
|
|
fn->file_attributes |= ni->flags & FILE_ATTR_HIDDEN;
|
|
fn->creation_time = ni->creation_time;
|
|
fn->last_data_change_time = ni->last_data_change_time;
|
|
fn->last_mft_change_time = ni->last_mft_change_time;
|
|
fn->last_access_time = ni->last_access_time;
|
|
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
|
|
fn->data_size = fn->allocated_size = const_cpu_to_sle64(0);
|
|
else {
|
|
fn->data_size = cpu_to_sle64(ni->data_size);
|
|
fn->allocated_size = cpu_to_sle64(ni->allocated_size);
|
|
}
|
|
memcpy(fn->file_name, name, name_len * sizeof(ntfschar));
|
|
/* Add FILE_NAME attribute to inode. */
|
|
if (ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8*)fn, fn_len)) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to add FILE_NAME attribute.\n");
|
|
goto err_out;
|
|
}
|
|
/* Add FILE_NAME attribute to index. */
|
|
if (ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no,
|
|
le16_to_cpu(ni->mrec->sequence_number)))) {
|
|
err = errno;
|
|
ntfs_log_perror("Failed to add entry to the index");
|
|
goto err_out;
|
|
}
|
|
/* Set hard links count and directory flag. */
|
|
ni->mrec->link_count = const_cpu_to_le16(1);
|
|
if (S_ISDIR(type))
|
|
ni->mrec->flags |= MFT_RECORD_IS_DIRECTORY;
|
|
ntfs_inode_mark_dirty(ni);
|
|
/* Done! */
|
|
free(fn);
|
|
free(si);
|
|
ntfs_log_trace("Done.\n");
|
|
return ni;
|
|
err_out:
|
|
ntfs_log_trace("Failed.\n");
|
|
|
|
if (rollback_sd)
|
|
ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0);
|
|
|
|
if (rollback_data)
|
|
ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0);
|
|
/*
|
|
* Free extent MFT records (should not exist any with current
|
|
* ntfs_create implementation, but for any case if something will be
|
|
* changed in the future).
|
|
*/
|
|
while (ni->nr_extents)
|
|
if (ntfs_mft_record_free(ni->vol, *(ni->extent_nis))) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to free extent MFT record. "
|
|
"Leaving inconsistent metadata.\n");
|
|
}
|
|
if (ntfs_mft_record_free(ni->vol, ni))
|
|
ntfs_log_error("Failed to free MFT record. "
|
|
"Leaving inconsistent metadata. Run chkdsk.\n");
|
|
free(fn);
|
|
free(si);
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Some wrappers around __ntfs_create() ...
|
|
*/
|
|
|
|
ntfs_inode *ntfs_create(ntfs_inode *dir_ni, le32 securid, const ntfschar *name,
|
|
u8 name_len, mode_t type)
|
|
{
|
|
if (type != S_IFREG && type != S_IFDIR && type != S_IFIFO &&
|
|
type != S_IFSOCK) {
|
|
ntfs_log_error("Invalid arguments.\n");
|
|
return NULL;
|
|
}
|
|
return __ntfs_create(dir_ni, securid, name, name_len, type, 0, NULL, 0);
|
|
}
|
|
|
|
ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, le32 securid,
|
|
const ntfschar *name, u8 name_len, mode_t type, dev_t dev)
|
|
{
|
|
if (type != S_IFCHR && type != S_IFBLK) {
|
|
ntfs_log_error("Invalid arguments.\n");
|
|
return NULL;
|
|
}
|
|
return __ntfs_create(dir_ni, securid, name, name_len, type, dev, NULL, 0);
|
|
}
|
|
|
|
ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, le32 securid,
|
|
const ntfschar *name, u8 name_len, const ntfschar *target,
|
|
int target_len)
|
|
{
|
|
if (!target || !target_len) {
|
|
ntfs_log_error("%s: Invalid argument (%p, %d)\n", __FUNCTION__,
|
|
target, target_len);
|
|
return NULL;
|
|
}
|
|
return __ntfs_create(dir_ni, securid, name, name_len, S_IFLNK, 0,
|
|
target, target_len);
|
|
}
|
|
|
|
int ntfs_check_empty_dir(ntfs_inode *ni)
|
|
{
|
|
ntfs_attr *na;
|
|
int ret = 0;
|
|
|
|
if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY))
|
|
return 0;
|
|
|
|
na = ntfs_attr_open(ni, AT_INDEX_ROOT, NTFS_INDEX_I30, 4);
|
|
if (!na) {
|
|
errno = EIO;
|
|
ntfs_log_perror("Failed to open directory");
|
|
return -1;
|
|
}
|
|
|
|
/* Non-empty directory? */
|
|
if ((na->data_size != sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY_HEADER))){
|
|
/* Both ENOTEMPTY and EEXIST are ok. We use the more common. */
|
|
errno = ENOTEMPTY;
|
|
ntfs_log_debug("Directory is not empty\n");
|
|
ret = -1;
|
|
}
|
|
|
|
ntfs_attr_close(na);
|
|
return ret;
|
|
}
|
|
|
|
static int ntfs_check_unlinkable_dir(ntfs_inode *ni, FILE_NAME_ATTR *fn)
|
|
{
|
|
int link_count = le16_to_cpu(ni->mrec->link_count);
|
|
int ret;
|
|
|
|
ret = ntfs_check_empty_dir(ni);
|
|
if (!ret || errno != ENOTEMPTY)
|
|
return ret;
|
|
/*
|
|
* Directory is non-empty, so we can unlink only if there is more than
|
|
* one "real" hard link, i.e. links aren't different DOS and WIN32 names
|
|
*/
|
|
if ((link_count == 1) ||
|
|
(link_count == 2 && fn->file_name_type == FILE_NAME_DOS)) {
|
|
errno = ENOTEMPTY;
|
|
ntfs_log_debug("Non-empty directory without hard links\n");
|
|
goto no_hardlink;
|
|
}
|
|
|
|
ret = 0;
|
|
no_hardlink:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ntfs_delete - delete file or directory from ntfs volume
|
|
* @ni: ntfs inode for object to delte
|
|
* @dir_ni: ntfs inode for directory in which delete object
|
|
* @name: unicode name of the object to delete
|
|
* @name_len: length of the name in unicode characters
|
|
*
|
|
* @ni is always closed after the call to this function (even if it failed),
|
|
* user does not need to call ntfs_inode_close himself.
|
|
*
|
|
* Return 0 on success or -1 on error with errno set to the error code.
|
|
*/
|
|
int ntfs_delete(ntfs_volume *vol, const char *pathname,
|
|
ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name,
|
|
u8 name_len)
|
|
{
|
|
ntfs_attr_search_ctx *actx = NULL;
|
|
FILE_NAME_ATTR *fn = NULL;
|
|
BOOL looking_for_dos_name = FALSE, looking_for_win32_name = FALSE;
|
|
BOOL case_sensitive_match = TRUE;
|
|
int err = 0;
|
|
#if CACHE_NIDATA_SIZE
|
|
int i;
|
|
#endif
|
|
#if CACHE_INODE_SIZE
|
|
struct CACHED_INODE item;
|
|
const char *p;
|
|
u64 inum = (u64)-1;
|
|
int count;
|
|
#endif
|
|
#if CACHE_LOOKUP_SIZE
|
|
struct CACHED_LOOKUP lkitem;
|
|
#endif
|
|
|
|
ntfs_log_trace("Entering.\n");
|
|
|
|
if (!ni || !dir_ni || !name || !name_len) {
|
|
ntfs_log_error("Invalid arguments.\n");
|
|
errno = EINVAL;
|
|
goto err_out;
|
|
}
|
|
if (ni->nr_extents == -1)
|
|
ni = ni->base_ni;
|
|
if (dir_ni->nr_extents == -1)
|
|
dir_ni = dir_ni->base_ni;
|
|
/*
|
|
* Search for FILE_NAME attribute with such name. If it's in POSIX or
|
|
* WIN32_AND_DOS namespace, then simply remove it from index and inode.
|
|
* If filename in DOS or in WIN32 namespace, then remove DOS name first,
|
|
* only then remove WIN32 name.
|
|
*/
|
|
actx = ntfs_attr_get_search_ctx(ni, NULL);
|
|
if (!actx)
|
|
goto err_out;
|
|
search:
|
|
while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
|
|
0, NULL, 0, actx)) {
|
|
char *s;
|
|
IGNORE_CASE_BOOL case_sensitive = IGNORE_CASE;
|
|
|
|
errno = 0;
|
|
fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
|
|
le16_to_cpu(actx->attr->value_offset));
|
|
s = ntfs_attr_name_get(fn->file_name, fn->file_name_length);
|
|
ntfs_log_trace("name: '%s' type: %d dos: %d win32: %d "
|
|
"case: %d\n", s, fn->file_name_type,
|
|
looking_for_dos_name, looking_for_win32_name,
|
|
case_sensitive_match);
|
|
ntfs_attr_name_free(&s);
|
|
if (looking_for_dos_name) {
|
|
if (fn->file_name_type == FILE_NAME_DOS)
|
|
break;
|
|
else
|
|
continue;
|
|
}
|
|
if (looking_for_win32_name) {
|
|
if (fn->file_name_type == FILE_NAME_WIN32)
|
|
break;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
/* Ignore hard links from other directories */
|
|
if (dir_ni->mft_no != MREF_LE(fn->parent_directory)) {
|
|
ntfs_log_debug("MFT record numbers don't match "
|
|
"(%llu != %llu)\n",
|
|
(long long unsigned)dir_ni->mft_no,
|
|
(long long unsigned)MREF_LE(fn->parent_directory));
|
|
continue;
|
|
}
|
|
if (case_sensitive_match
|
|
|| ((fn->file_name_type == FILE_NAME_POSIX)
|
|
&& NVolCaseSensitive(ni->vol)))
|
|
case_sensitive = CASE_SENSITIVE;
|
|
|
|
if (ntfs_names_are_equal(fn->file_name, fn->file_name_length,
|
|
name, name_len, case_sensitive,
|
|
ni->vol->upcase, ni->vol->upcase_len)){
|
|
|
|
if (fn->file_name_type == FILE_NAME_WIN32) {
|
|
looking_for_dos_name = TRUE;
|
|
ntfs_attr_reinit_search_ctx(actx);
|
|
continue;
|
|
}
|
|
if (fn->file_name_type == FILE_NAME_DOS)
|
|
looking_for_dos_name = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (errno) {
|
|
/*
|
|
* If case sensitive search failed, then try once again
|
|
* ignoring case.
|
|
*/
|
|
if (errno == ENOENT && case_sensitive_match) {
|
|
case_sensitive_match = FALSE;
|
|
ntfs_attr_reinit_search_ctx(actx);
|
|
goto search;
|
|
}
|
|
goto err_out;
|
|
}
|
|
|
|
if (ntfs_check_unlinkable_dir(ni, fn) < 0)
|
|
goto err_out;
|
|
|
|
if (ntfs_index_remove(dir_ni, ni, fn, le32_to_cpu(actx->attr->value_length)))
|
|
goto err_out;
|
|
|
|
/*
|
|
* Keep the last name in place, this is useful for undeletion
|
|
* (Windows also does so), however delete the name if it were
|
|
* in an extent, to avoid leaving an attribute list.
|
|
*/
|
|
if ((ni->mrec->link_count == const_cpu_to_le16(1)) && !actx->base_ntfs_ino) {
|
|
/* make sure to not loop to another search */
|
|
looking_for_dos_name = FALSE;
|
|
} else {
|
|
if (ntfs_attr_record_rm(actx))
|
|
goto err_out;
|
|
}
|
|
|
|
ni->mrec->link_count = cpu_to_le16(le16_to_cpu(
|
|
ni->mrec->link_count) - 1);
|
|
|
|
ntfs_inode_mark_dirty(ni);
|
|
if (looking_for_dos_name) {
|
|
looking_for_dos_name = FALSE;
|
|
looking_for_win32_name = TRUE;
|
|
ntfs_attr_reinit_search_ctx(actx);
|
|
goto search;
|
|
}
|
|
/* TODO: Update object id, quota and securiry indexes if required. */
|
|
/*
|
|
* If hard link count is not equal to zero then we are done. In other
|
|
* case there are no reference to this inode left, so we should free all
|
|
* non-resident attributes and mark all MFT record as not in use.
|
|
*/
|
|
#if CACHE_LOOKUP_SIZE
|
|
/* invalidate entry in lookup cache */
|
|
lkitem.name = (const char*)NULL;
|
|
lkitem.namesize = 0;
|
|
lkitem.inum = ni->mft_no;
|
|
lkitem.parent = dir_ni->mft_no;
|
|
ntfs_invalidate_cache(vol->lookup_cache, GENERIC(&lkitem),
|
|
lookup_cache_inv_compare, CACHE_NOHASH);
|
|
#endif
|
|
#if CACHE_INODE_SIZE
|
|
inum = ni->mft_no;
|
|
if (pathname) {
|
|
/* invalide cache entry, even if there was an error */
|
|
/* Remove leading /'s. */
|
|
p = pathname;
|
|
while (*p == PATH_SEP)
|
|
p++;
|
|
if (p[0] && (p[strlen(p)-1] == PATH_SEP))
|
|
ntfs_log_error("Unnormalized path %s\n",pathname);
|
|
item.pathname = p;
|
|
item.varsize = strlen(p);
|
|
} else {
|
|
item.pathname = (const char*)NULL;
|
|
item.varsize = 0;
|
|
}
|
|
item.inum = inum;
|
|
count = ntfs_invalidate_cache(vol->xinode_cache, GENERIC(&item),
|
|
inode_cache_inv_compare, CACHE_NOHASH);
|
|
if (pathname && !count)
|
|
ntfs_log_error("Could not delete inode cache entry for %s\n",
|
|
pathname);
|
|
#endif
|
|
if (ni->mrec->link_count) {
|
|
ntfs_inode_update_times(ni, NTFS_UPDATE_CTIME);
|
|
goto ok;
|
|
}
|
|
if (ntfs_delete_reparse_index(ni)) {
|
|
/*
|
|
* Failed to remove the reparse index : proceed anyway
|
|
* This is not a critical error, the entry is useless
|
|
* because of sequence_number, and stopping file deletion
|
|
* would be much worse as the file is not referenced now.
|
|
*/
|
|
err = errno;
|
|
}
|
|
if (ntfs_delete_object_id_index(ni)) {
|
|
/*
|
|
* Failed to remove the object id index : proceed anyway
|
|
* This is not a critical error.
|
|
*/
|
|
err = errno;
|
|
}
|
|
ntfs_attr_reinit_search_ctx(actx);
|
|
while (!ntfs_attrs_walk(actx)) {
|
|
if (actx->attr->non_resident) {
|
|
runlist *rl;
|
|
|
|
rl = ntfs_mapping_pairs_decompress(ni->vol, actx->attr,
|
|
NULL);
|
|
if (!rl) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to decompress runlist. "
|
|
"Leaving inconsistent metadata.\n");
|
|
continue;
|
|
}
|
|
if (ntfs_cluster_free_from_rl(ni->vol, rl)) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to free clusters. "
|
|
"Leaving inconsistent metadata.\n");
|
|
continue;
|
|
}
|
|
free(rl);
|
|
}
|
|
}
|
|
if (errno != ENOENT) {
|
|
err = errno;
|
|
ntfs_log_error("Attribute enumeration failed. "
|
|
"Probably leaving inconsistent metadata.\n");
|
|
}
|
|
/* All extents should be attached after attribute walk. */
|
|
#if CACHE_NIDATA_SIZE
|
|
/*
|
|
* Disconnect extents before deleting them, so they are
|
|
* not wrongly moved to cache through the chainings
|
|
*/
|
|
for (i=ni->nr_extents-1; i>=0; i--) {
|
|
ni->extent_nis[i]->base_ni = (ntfs_inode*)NULL;
|
|
ni->extent_nis[i]->nr_extents = 0;
|
|
if (ntfs_mft_record_free(ni->vol, ni->extent_nis[i])) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to free extent MFT record. "
|
|
"Leaving inconsistent metadata.\n");
|
|
}
|
|
}
|
|
free(ni->extent_nis);
|
|
ni->nr_extents = 0;
|
|
ni->extent_nis = (ntfs_inode**)NULL;
|
|
#else
|
|
while (ni->nr_extents)
|
|
if (ntfs_mft_record_free(ni->vol, *(ni->extent_nis))) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to free extent MFT record. "
|
|
"Leaving inconsistent metadata.\n");
|
|
}
|
|
#endif
|
|
debug_double_inode(ni->mft_no,0);
|
|
if (ntfs_mft_record_free(ni->vol, ni)) {
|
|
err = errno;
|
|
ntfs_log_error("Failed to free base MFT record. "
|
|
"Leaving inconsistent metadata.\n");
|
|
}
|
|
ni = NULL;
|
|
ok:
|
|
ntfs_inode_update_times(dir_ni, NTFS_UPDATE_MCTIME);
|
|
out:
|
|
if (actx)
|
|
ntfs_attr_put_search_ctx(actx);
|
|
if (ntfs_inode_close(dir_ni) && !err)
|
|
err = errno;
|
|
if (ntfs_inode_close(ni) && !err)
|
|
err = errno;
|
|
if (err) {
|
|
errno = err;
|
|
ntfs_log_debug("Could not delete file: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
ntfs_log_trace("Done.\n");
|
|
return 0;
|
|
err_out:
|
|
err = errno;
|
|
goto out;
|
|
}
|
|
|
|
/**
|
|
* ntfs_link - create hard link for file or directory
|
|
* @ni: ntfs inode for object to create hard link
|
|
* @dir_ni: ntfs inode for directory in which new link should be placed
|
|
* @name: unicode name of the new link
|
|
* @name_len: length of the name in unicode characters
|
|
*
|
|
* NOTE: At present we allow creating hardlinks to directories, we use them
|
|
* in a temporary state during rename. But it's defenitely bad idea to have
|
|
* hard links to directories as a result of operation.
|
|
* FIXME: Create internal __ntfs_link that allows hard links to a directories
|
|
* and external ntfs_link that do not. Write ntfs_rename that uses __ntfs_link.
|
|
*
|
|
* Return 0 on success or -1 on error with errno set to the error code.
|
|
*/
|
|
static int ntfs_link_i(ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name,
|
|
u8 name_len, FILE_NAME_TYPE_FLAGS nametype)
|
|
{
|
|
FILE_NAME_ATTR *fn = NULL;
|
|
int fn_len, err;
|
|
|
|
ntfs_log_trace("Entering.\n");
|
|
|
|
if (!ni || !dir_ni || !name || !name_len ||
|
|
ni->mft_no == dir_ni->mft_no) {
|
|
err = EINVAL;
|
|
ntfs_log_perror("ntfs_link wrong arguments");
|
|
goto err_out;
|
|
}
|
|
|
|
if (NVolHideDotFiles(dir_ni->vol)) {
|
|
/* Set hidden flag according to the latest name */
|
|
if ((name_len > 1)
|
|
&& (name[0] == const_cpu_to_le16('.'))
|
|
&& (name[1] != const_cpu_to_le16('.')))
|
|
ni->flags |= FILE_ATTR_HIDDEN;
|
|
else
|
|
ni->flags &= ~FILE_ATTR_HIDDEN;
|
|
}
|
|
|
|
/* Create FILE_NAME attribute. */
|
|
fn_len = sizeof(FILE_NAME_ATTR) + name_len * sizeof(ntfschar);
|
|
fn = ntfs_calloc(fn_len);
|
|
if (!fn) {
|
|
err = errno;
|
|
goto err_out;
|
|
}
|
|
fn->parent_directory = MK_LE_MREF(dir_ni->mft_no,
|
|
le16_to_cpu(dir_ni->mrec->sequence_number));
|
|
fn->file_name_length = name_len;
|
|
fn->file_name_type = nametype;
|
|
fn->file_attributes = ni->flags;
|
|
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
|
|
fn->file_attributes |= FILE_ATTR_I30_INDEX_PRESENT;
|
|
fn->data_size = fn->allocated_size = const_cpu_to_sle64(0);
|
|
} else {
|
|
fn->allocated_size = cpu_to_sle64(ni->allocated_size);
|
|
fn->data_size = cpu_to_sle64(ni->data_size);
|
|
}
|
|
fn->creation_time = ni->creation_time;
|
|
fn->last_data_change_time = ni->last_data_change_time;
|
|
fn->last_mft_change_time = ni->last_mft_change_time;
|
|
fn->last_access_time = ni->last_access_time;
|
|
memcpy(fn->file_name, name, name_len * sizeof(ntfschar));
|
|
/* Add FILE_NAME attribute to index. */
|
|
if (ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no,
|
|
le16_to_cpu(ni->mrec->sequence_number)))) {
|
|
err = errno;
|
|
ntfs_log_perror("Failed to add filename to the index");
|
|
goto err_out;
|
|
}
|
|
/* Add FILE_NAME attribute to inode. */
|
|
if (ntfs_attr_add(ni, AT_FILE_NAME, AT_UNNAMED, 0, (u8*)fn, fn_len)) {
|
|
ntfs_log_error("Failed to add FILE_NAME attribute.\n");
|
|
err = errno;
|
|
/* Try to remove just added attribute from index. */
|
|
if (ntfs_index_remove(dir_ni, ni, fn, fn_len))
|
|
goto rollback_failed;
|
|
goto err_out;
|
|
}
|
|
/* Increment hard links count. */
|
|
ni->mrec->link_count = cpu_to_le16(le16_to_cpu(
|
|
ni->mrec->link_count) + 1);
|
|
/* Done! */
|
|
ntfs_inode_mark_dirty(ni);
|
|
free(fn);
|
|
ntfs_log_trace("Done.\n");
|
|
return 0;
|
|
rollback_failed:
|
|
ntfs_log_error("Rollback failed. Leaving inconsistent metadata.\n");
|
|
err_out:
|
|
free(fn);
|
|
errno = err;
|
|
return -1;
|
|
}
|
|
|
|
int ntfs_link(ntfs_inode *ni, ntfs_inode *dir_ni, const ntfschar *name,
|
|
u8 name_len)
|
|
{
|
|
return (ntfs_link_i(ni, dir_ni, name, name_len, FILE_NAME_POSIX));
|
|
}
|
|
|
|
/*
|
|
* Get a parent directory from an inode entry
|
|
*
|
|
* This is only used in situations where the path used to access
|
|
* the current file is not known for sure. The result may be different
|
|
* from the path when the file is linked in several parent directories.
|
|
*
|
|
* Currently this is only used for translating ".." in the target
|
|
* of a Vista relative symbolic link
|
|
*/
|
|
|
|
ntfs_inode *ntfs_dir_parent_inode(ntfs_inode *ni)
|
|
{
|
|
ntfs_inode *dir_ni = (ntfs_inode*)NULL;
|
|
u64 inum;
|
|
FILE_NAME_ATTR *fn;
|
|
ntfs_attr_search_ctx *ctx;
|
|
|
|
if (ni->mft_no != FILE_root) {
|
|
/* find the name in the attributes */
|
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
|
if (!ctx)
|
|
return ((ntfs_inode*)NULL);
|
|
|
|
if (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
|
|
CASE_SENSITIVE, 0, NULL, 0, ctx)) {
|
|
/* We know this will always be resident. */
|
|
fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
inum = le64_to_cpu(fn->parent_directory);
|
|
if (inum != (u64)-1) {
|
|
dir_ni = ntfs_inode_open(ni->vol, MREF(inum));
|
|
}
|
|
}
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
}
|
|
return (dir_ni);
|
|
}
|
|
|
|
#ifdef HAVE_SETXATTR
|
|
|
|
#define MAX_DOS_NAME_LENGTH 12
|
|
|
|
/*
|
|
* Get a DOS name for a file in designated directory
|
|
*
|
|
* Not allowed if there are several non-dos names (EMLINK)
|
|
*
|
|
* Returns size if found
|
|
* 0 if not found
|
|
* -1 if there was an error (described by errno)
|
|
*/
|
|
|
|
static int get_dos_name(ntfs_inode *ni, u64 dnum, ntfschar *dosname)
|
|
{
|
|
size_t outsize = 0;
|
|
int namecount = 0;
|
|
FILE_NAME_ATTR *fn;
|
|
ntfs_attr_search_ctx *ctx;
|
|
|
|
/* find the name in the attributes */
|
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
|
|
0, NULL, 0, ctx)) {
|
|
/* We know this will always be resident. */
|
|
fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
|
|
if (fn->file_name_type != FILE_NAME_DOS)
|
|
namecount++;
|
|
if ((fn->file_name_type & FILE_NAME_DOS)
|
|
&& (MREF_LE(fn->parent_directory) == dnum)) {
|
|
/*
|
|
* Found a DOS or WIN32+DOS name for the entry
|
|
* copy name, after truncation for safety
|
|
*/
|
|
outsize = fn->file_name_length;
|
|
/* TODO : reject if name is too long ? */
|
|
if (outsize > MAX_DOS_NAME_LENGTH)
|
|
outsize = MAX_DOS_NAME_LENGTH;
|
|
memcpy(dosname,fn->file_name,outsize*sizeof(ntfschar));
|
|
}
|
|
}
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
if ((outsize > 0) && (namecount > 1)) {
|
|
outsize = -1;
|
|
errno = EMLINK; /* this error implies there is a dos name */
|
|
}
|
|
return (outsize);
|
|
}
|
|
|
|
|
|
/*
|
|
* Get a long name for a file in designated directory
|
|
*
|
|
* Not allowed if there are several non-dos names (EMLINK)
|
|
*
|
|
* Returns size if found
|
|
* 0 if not found
|
|
* -1 if there was an error (described by errno)
|
|
*/
|
|
|
|
static int get_long_name(ntfs_inode *ni, u64 dnum, ntfschar *longname)
|
|
{
|
|
size_t outsize = 0;
|
|
int namecount = 0;
|
|
FILE_NAME_ATTR *fn;
|
|
ntfs_attr_search_ctx *ctx;
|
|
|
|
/* find the name in the attributes */
|
|
ctx = ntfs_attr_get_search_ctx(ni, NULL);
|
|
if (!ctx)
|
|
return -1;
|
|
|
|
/* first search for WIN32 or DOS+WIN32 names */
|
|
while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
|
|
0, NULL, 0, ctx)) {
|
|
/* We know this will always be resident. */
|
|
fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
|
|
if (fn->file_name_type != FILE_NAME_DOS)
|
|
namecount++;
|
|
if ((fn->file_name_type & FILE_NAME_WIN32)
|
|
&& (MREF_LE(fn->parent_directory) == dnum)) {
|
|
/*
|
|
* Found a WIN32 or WIN32+DOS name for the entry
|
|
* copy name
|
|
*/
|
|
outsize = fn->file_name_length;
|
|
memcpy(longname,fn->file_name,outsize*sizeof(ntfschar));
|
|
}
|
|
}
|
|
if (namecount > 1) {
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
errno = EMLINK;
|
|
return -1;
|
|
}
|
|
/* if not found search for POSIX names */
|
|
if (!outsize) {
|
|
ntfs_attr_reinit_search_ctx(ctx);
|
|
while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
|
|
0, NULL, 0, ctx)) {
|
|
/* We know this will always be resident. */
|
|
fn = (FILE_NAME_ATTR*)((u8*)ctx->attr +
|
|
le16_to_cpu(ctx->attr->value_offset));
|
|
|
|
if ((fn->file_name_type == FILE_NAME_POSIX)
|
|
&& (MREF_LE(fn->parent_directory) == dnum)) {
|
|
/*
|
|
* Found a POSIX name for the entry
|
|
* copy name
|
|
*/
|
|
outsize = fn->file_name_length;
|
|
memcpy(longname,fn->file_name,outsize*sizeof(ntfschar));
|
|
}
|
|
}
|
|
}
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
return (outsize);
|
|
}
|
|
|
|
|
|
/*
|
|
* Get the ntfs DOS name into an extended attribute
|
|
*/
|
|
|
|
int ntfs_get_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
|
|
char *value, size_t size)
|
|
{
|
|
int outsize = 0;
|
|
char *outname = (char*)NULL;
|
|
u64 dnum;
|
|
int doslen;
|
|
ntfschar dosname[MAX_DOS_NAME_LENGTH];
|
|
|
|
dnum = dir_ni->mft_no;
|
|
doslen = get_dos_name(ni, dnum, dosname);
|
|
if (doslen > 0) {
|
|
/*
|
|
* Found a DOS name for the entry, make
|
|
* uppercase and encode into the buffer
|
|
* if there is enough space
|
|
*/
|
|
ntfs_name_upcase(dosname, doslen,
|
|
ni->vol->upcase, ni->vol->upcase_len);
|
|
if (ntfs_ucstombs(dosname, doslen, &outname, size) < 0) {
|
|
ntfs_log_error("Cannot represent dosname in current locale.\n");
|
|
outsize = -errno;
|
|
} else {
|
|
outsize = strlen(outname);
|
|
if (value && (outsize <= (int)size))
|
|
memcpy(value, outname, outsize);
|
|
else
|
|
if (size && (outsize > (int)size))
|
|
outsize = -ERANGE;
|
|
free(outname);
|
|
}
|
|
} else {
|
|
if (doslen == 0)
|
|
errno = ENODATA;
|
|
outsize = -errno;
|
|
}
|
|
return (outsize);
|
|
}
|
|
|
|
/*
|
|
* Change the name space of an existing file or directory
|
|
*
|
|
* Returns the old namespace if successful
|
|
* -1 if an error occurred (described by errno)
|
|
*/
|
|
|
|
static int set_namespace(ntfs_inode *ni, ntfs_inode *dir_ni,
|
|
const ntfschar *name, int len,
|
|
FILE_NAME_TYPE_FLAGS nametype)
|
|
{
|
|
ntfs_attr_search_ctx *actx;
|
|
ntfs_index_context *icx;
|
|
FILE_NAME_ATTR *fnx;
|
|
FILE_NAME_ATTR *fn = NULL;
|
|
BOOL found;
|
|
int lkup;
|
|
int ret;
|
|
|
|
ret = -1;
|
|
actx = ntfs_attr_get_search_ctx(ni, NULL);
|
|
if (actx) {
|
|
found = FALSE;
|
|
do {
|
|
lkup = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
|
|
CASE_SENSITIVE, 0, NULL, 0, actx);
|
|
if (!lkup) {
|
|
fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
|
|
le16_to_cpu(actx->attr->value_offset));
|
|
found = (MREF_LE(fn->parent_directory)
|
|
== dir_ni->mft_no)
|
|
&& !memcmp(fn->file_name, name,
|
|
len*sizeof(ntfschar));
|
|
}
|
|
} while (!lkup && !found);
|
|
if (found) {
|
|
icx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4);
|
|
if (icx) {
|
|
lkup = ntfs_index_lookup((char*)fn, len, icx);
|
|
if (!lkup && icx->data && icx->data_len) {
|
|
fnx = (FILE_NAME_ATTR*)icx->data;
|
|
ret = fn->file_name_type;
|
|
fn->file_name_type = nametype;
|
|
fnx->file_name_type = nametype;
|
|
ntfs_inode_mark_dirty(ni);
|
|
ntfs_index_entry_mark_dirty(icx);
|
|
}
|
|
ntfs_index_ctx_put(icx);
|
|
}
|
|
}
|
|
ntfs_attr_put_search_ctx(actx);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Set a DOS name to a file and adjust name spaces
|
|
*
|
|
* If the new names are collapsible (same uppercased chars) :
|
|
*
|
|
* - the existing DOS name or DOS+Win32 name is made Posix
|
|
* - if it was a real DOS name, the existing long name is made DOS+Win32
|
|
* and the existing DOS name is deleted
|
|
* - finally the existing long name is made DOS+Win32 unless already done
|
|
*
|
|
* If the new names are not collapsible :
|
|
*
|
|
* - insert the short name as a DOS name
|
|
* - delete the old long name or existing short name
|
|
* - insert the new long name (as a Win32 or DOS+Win32 name)
|
|
*
|
|
* Deleting the old long name will not delete the file
|
|
* provided the old name was in the Posix name space,
|
|
* because the alternate name has been set before.
|
|
*
|
|
* The inodes of file and parent directory are always closed
|
|
*
|
|
* Returns 0 if successful
|
|
* -1 if failed
|
|
*/
|
|
|
|
static int set_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
|
|
const ntfschar *shortname, int shortlen,
|
|
const ntfschar *longname, int longlen,
|
|
const ntfschar *deletename, int deletelen, BOOL existed)
|
|
{
|
|
unsigned int linkcount;
|
|
ntfs_volume *vol;
|
|
BOOL collapsible;
|
|
BOOL deleted;
|
|
BOOL done;
|
|
FILE_NAME_TYPE_FLAGS oldnametype;
|
|
u64 dnum;
|
|
u64 fnum;
|
|
int res;
|
|
|
|
res = -1;
|
|
vol = ni->vol;
|
|
dnum = dir_ni->mft_no;
|
|
fnum = ni->mft_no;
|
|
/* save initial link count */
|
|
linkcount = le16_to_cpu(ni->mrec->link_count);
|
|
|
|
/* check whether the same name may be used as DOS and WIN32 */
|
|
collapsible = ntfs_collapsible_chars(ni->vol, shortname, shortlen,
|
|
longname, longlen);
|
|
if (collapsible) {
|
|
deleted = FALSE;
|
|
done = FALSE;
|
|
if (existed) {
|
|
oldnametype = set_namespace(ni, dir_ni, deletename,
|
|
deletelen, FILE_NAME_POSIX);
|
|
if (oldnametype == FILE_NAME_DOS) {
|
|
if (set_namespace(ni, dir_ni, longname, longlen,
|
|
FILE_NAME_WIN32_AND_DOS) >= 0) {
|
|
if (!ntfs_delete(vol,
|
|
(const char*)NULL, ni, dir_ni,
|
|
deletename, deletelen))
|
|
res = 0;
|
|
deleted = TRUE;
|
|
} else
|
|
done = TRUE;
|
|
}
|
|
}
|
|
if (!deleted) {
|
|
if (!done && (set_namespace(ni, dir_ni,
|
|
longname, longlen,
|
|
FILE_NAME_WIN32_AND_DOS) >= 0))
|
|
res = 0;
|
|
ntfs_inode_update_times(ni, NTFS_UPDATE_CTIME);
|
|
ntfs_inode_update_times(dir_ni, NTFS_UPDATE_MCTIME);
|
|
if (ntfs_inode_close_in_dir(ni,dir_ni) && !res)
|
|
res = -1;
|
|
if (ntfs_inode_close(dir_ni) && !res)
|
|
res = -1;
|
|
}
|
|
} else {
|
|
if (!ntfs_link_i(ni, dir_ni, shortname, shortlen,
|
|
FILE_NAME_DOS)
|
|
/* make sure a new link was recorded */
|
|
&& (le16_to_cpu(ni->mrec->link_count) > linkcount)) {
|
|
/* delete the existing long name or short name */
|
|
// is it ok to not provide the path ?
|
|
if (!ntfs_delete(vol, (char*)NULL, ni, dir_ni,
|
|
deletename, deletelen)) {
|
|
/* delete closes the inodes, so have to open again */
|
|
dir_ni = ntfs_inode_open(vol, dnum);
|
|
if (dir_ni) {
|
|
ni = ntfs_inode_open(vol, fnum);
|
|
if (ni) {
|
|
if (!ntfs_link_i(ni, dir_ni,
|
|
longname, longlen,
|
|
FILE_NAME_WIN32))
|
|
res = 0;
|
|
if (ntfs_inode_close_in_dir(ni,
|
|
dir_ni)
|
|
&& !res)
|
|
res = -1;
|
|
}
|
|
if (ntfs_inode_close(dir_ni) && !res)
|
|
res = -1;
|
|
}
|
|
}
|
|
} else {
|
|
ntfs_inode_close_in_dir(ni,dir_ni);
|
|
ntfs_inode_close(dir_ni);
|
|
}
|
|
}
|
|
return (res);
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the ntfs DOS name into an extended attribute
|
|
*
|
|
* The DOS name will be added as another file name attribute
|
|
* using the existing file name information from the original
|
|
* name or overwriting the DOS Name if one exists.
|
|
*
|
|
* The inode of the file is always closed
|
|
*/
|
|
|
|
int ntfs_set_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
|
|
const char *value, size_t size, int flags)
|
|
{
|
|
int res = 0;
|
|
int longlen = 0;
|
|
int shortlen = 0;
|
|
char newname[3*MAX_DOS_NAME_LENGTH + 1];
|
|
ntfschar oldname[MAX_DOS_NAME_LENGTH];
|
|
int oldlen;
|
|
u64 dnum;
|
|
BOOL closed = FALSE;
|
|
ntfschar *shortname = NULL;
|
|
ntfschar longname[NTFS_MAX_NAME_LEN];
|
|
|
|
/* copy the string to insert a null char, and truncate */
|
|
if (size > 3*MAX_DOS_NAME_LENGTH)
|
|
size = 3*MAX_DOS_NAME_LENGTH;
|
|
strncpy(newname, value, size);
|
|
/* a long name may be truncated badly and be untranslatable */
|
|
newname[size] = 0;
|
|
/* convert the string to the NTFS wide chars, and truncate */
|
|
shortlen = ntfs_mbstoucs(newname, &shortname);
|
|
if (shortlen > MAX_DOS_NAME_LENGTH)
|
|
shortlen = MAX_DOS_NAME_LENGTH;
|
|
/* make sure the short name has valid chars */
|
|
if ((shortlen < 0)
|
|
|| ntfs_forbidden_names(ni->vol,shortname,shortlen)) {
|
|
ntfs_inode_close_in_dir(ni,dir_ni);
|
|
ntfs_inode_close(dir_ni);
|
|
res = -errno;
|
|
return res;
|
|
}
|
|
dnum = dir_ni->mft_no;
|
|
longlen = get_long_name(ni, dnum, longname);
|
|
if (longlen > 0) {
|
|
oldlen = get_dos_name(ni, dnum, oldname);
|
|
if ((oldlen >= 0)
|
|
&& !ntfs_forbidden_names(ni->vol, longname, longlen)) {
|
|
if (oldlen > 0) {
|
|
if (flags & XATTR_CREATE) {
|
|
res = -1;
|
|
errno = EEXIST;
|
|
} else
|
|
if ((shortlen == oldlen)
|
|
&& !memcmp(shortname,oldname,
|
|
oldlen*sizeof(ntfschar)))
|
|
/* already set, done */
|
|
res = 0;
|
|
else {
|
|
res = set_dos_name(ni, dir_ni,
|
|
shortname, shortlen,
|
|
longname, longlen,
|
|
oldname, oldlen, TRUE);
|
|
closed = TRUE;
|
|
}
|
|
} else {
|
|
if (flags & XATTR_REPLACE) {
|
|
res = -1;
|
|
errno = ENODATA;
|
|
} else {
|
|
res = set_dos_name(ni, dir_ni,
|
|
shortname, shortlen,
|
|
longname, longlen,
|
|
longname, longlen, FALSE);
|
|
closed = TRUE;
|
|
}
|
|
}
|
|
} else
|
|
res = -1;
|
|
} else {
|
|
res = -1;
|
|
if (!longlen)
|
|
errno = ENOENT;
|
|
}
|
|
free(shortname);
|
|
if (!closed) {
|
|
ntfs_inode_close_in_dir(ni,dir_ni);
|
|
ntfs_inode_close(dir_ni);
|
|
}
|
|
return (res ? -1 : 0);
|
|
}
|
|
|
|
/*
|
|
* Delete the ntfs DOS name
|
|
*/
|
|
|
|
int ntfs_remove_ntfs_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni)
|
|
{
|
|
int res;
|
|
int oldnametype;
|
|
int longlen = 0;
|
|
int shortlen;
|
|
u64 dnum;
|
|
ntfs_volume *vol;
|
|
BOOL deleted = FALSE;
|
|
ntfschar shortname[MAX_DOS_NAME_LENGTH];
|
|
ntfschar longname[NTFS_MAX_NAME_LEN];
|
|
|
|
res = -1;
|
|
vol = ni->vol;
|
|
dnum = dir_ni->mft_no;
|
|
longlen = get_long_name(ni, dnum, longname);
|
|
if (longlen > 0) {
|
|
shortlen = get_dos_name(ni, dnum, shortname);
|
|
if (shortlen >= 0) {
|
|
/* migrate the long name as Posix */
|
|
oldnametype = set_namespace(ni,dir_ni,longname,longlen,
|
|
FILE_NAME_POSIX);
|
|
switch (oldnametype) {
|
|
case FILE_NAME_WIN32_AND_DOS :
|
|
/* name was Win32+DOS : done */
|
|
res = 0;
|
|
break;
|
|
case FILE_NAME_DOS :
|
|
/* name was DOS, make it back to DOS */
|
|
set_namespace(ni,dir_ni,longname,longlen,
|
|
FILE_NAME_DOS);
|
|
errno = ENOENT;
|
|
break;
|
|
case FILE_NAME_WIN32 :
|
|
/* name was Win32, make it Posix and delete */
|
|
if (set_namespace(ni,dir_ni,shortname,shortlen,
|
|
FILE_NAME_POSIX) >= 0) {
|
|
if (!ntfs_delete(vol,
|
|
(const char*)NULL, ni,
|
|
dir_ni, shortname,
|
|
shortlen))
|
|
res = 0;
|
|
deleted = TRUE;
|
|
} else {
|
|
/*
|
|
* DOS name has been found, but cannot
|
|
* migrate to Posix : something bad
|
|
* has happened
|
|
*/
|
|
errno = EIO;
|
|
ntfs_log_error("Could not change"
|
|
" DOS name of inode %lld to Posix\n",
|
|
(long long)ni->mft_no);
|
|
}
|
|
break;
|
|
default :
|
|
/* name was Posix or not found : error */
|
|
errno = ENOENT;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (!longlen)
|
|
errno = ENOENT;
|
|
res = -1;
|
|
}
|
|
if (!deleted) {
|
|
ntfs_inode_close_in_dir(ni,dir_ni);
|
|
ntfs_inode_close(dir_ni);
|
|
}
|
|
return (res);
|
|
}
|
|
|
|
#endif
|