#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_TYPES_H
#include <sys/types.h>
#endif
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#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"
#include "xattrs.h"
#include "ea.h"
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
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));
}
static int inode_cache_compare(const struct CACHED_GENERIC *cached,
const struct CACHED_GENERIC *wanted)
{
return (!cached->variable
|| strcmp(cached->variable, wanted->variable));
}
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
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));
}
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)));
}
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
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;
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);
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);
ie = (INDEX_ENTRY*)((u8*)&ir->index +
le32_to_cpu(ir->index.entries_offset));
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie +
sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->length) >
index_end) {
ntfs_log_error("Index root entry out of bounds in"
" inode %lld\n",
(unsigned long long)dir_ni->mft_no);
goto put_err_out;
}
if (ie->ie_flags & INDEX_ENTRY_END)
break;
if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
dir_ni->mft_no)) {
errno = EIO;
goto put_err_out;
}
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 (rc == -1)
break;
if (rc)
continue;
mref = le64_to_cpu(ie->indexed_file);
ntfs_attr_put_search_ctx(ctx);
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;
}
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;
}
ia = ntfs_malloc(index_block_size);
if (!ia) {
ntfs_attr_close(ia_na);
goto put_err_out;
}
if (vol->cluster_size <= index_block_size) {
index_vcn_size_bits = vol->cluster_size_bits;
} else {
index_vcn_size_bits = NTFS_BLOCK_SIZE_BITS;
}
vcn = sle64_to_cpup((sle64*)((u8*)ie + le16_to_cpu(ie->length) - 8));
descend_into_child_node:
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 from inode %lld",
(unsigned long long)vcn,
(unsigned long long)ia_na->ni->mft_no);
goto close_err_out;
}
if (ntfs_index_block_inconsistent((INDEX_BLOCK*)ia, index_block_size,
ia_na->ni->mft_no, vcn)) {
errno = EIO;
goto close_err_out;
}
index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);
ie = (INDEX_ENTRY*)((u8*)&ia->index +
le32_to_cpu(ia->index.entries_offset));
for (;; ie = (INDEX_ENTRY*)((u8*)ie + le16_to_cpu(ie->length))) {
if ((u8*)ie < (u8*)ia || (u8*)ie +
sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->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;
}
if (ie->ie_flags & INDEX_ENTRY_END)
break;
if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
dir_ni->mft_no)) {
errno = EIO;
goto close_err_out;
}
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 (rc == -1)
break;
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;
}
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;
}
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);
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;
}
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
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 {
uname_len = ntfs_mbstoucs(name, &uname);
if (uname_len >= 0) {
inum = ntfs_inode_lookup_by_name(dir_ni,
uname, uname_len);
item.inum = inum;
ntfs_enter_cache(dir_ni->vol->lookup_cache,
GENERIC(&item),
lookup_cache_compare);
free(uname);
} else
inum = (s64)-1;
}
} else
#endif
{
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);
}
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_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;
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
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) {
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) {
q = strchr(p, PATH_SEP);
if (q != NULL) {
*q = '\0';
}
#if CACHE_INODE_SIZE
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 (!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;
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;
}
static const ntfschar dotdot[3] = { const_cpu_to_le16('.'),
const_cpu_to_le16('.'),
const_cpu_to_le16('\0') };
typedef union {
INDEX_ROOT *ir;
INDEX_ALLOCATION *ia;
} index_union __attribute__((__transparent_union__));
typedef enum {
INDEX_TYPE_ROOT,
INDEX_TYPE_ALLOCATION,
} INDEX_TYPE;
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) {
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);
}
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)
dt_type = (ntfs_possible_symlink(ni)
? NTFS_DT_LNK : NTFS_DT_REPARSE);
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)) {
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);
}
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");
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
*pos = (u8*)ie - (u8*)iu.ir;
mref = le64_to_cpu(ie->indexed_file);
metadata = (MREF(mref) != FILE_root) && (MREF(mref) < FILE_first_user);
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;
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);
}
#ifndef __HAIKU__
static
#endif
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));
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);
}
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);
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;
if (*pos >= i_size + vol->mft_record_size)
goto done;
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;
ir_pos = (int)*pos;
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;
}
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 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;
}
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);
ie = (INDEX_ENTRY*)((u8*)&ir->index +
le32_to_cpu(ir->index.entries_offset));
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));
if ((u8*)ie < (u8*)ctx->mrec || (u8*)ie +
sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->length) >
index_end) {
ntfs_log_error("Index root entry out of bounds in"
" inode %lld\n",
(unsigned long long)dir_ni->mft_no);
goto dir_err_out;
}
if (ie->ie_flags & INDEX_ENTRY_END)
break;
if (!le16_to_cpu(ie->length))
goto dir_err_out;
if (ir_pos > (u8*)ie - (u8*)ir)
continue;
if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
dir_ni->mft_no)) {
errno = EIO;
goto dir_err_out;
}
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 (!ia_na)
goto EOD;
*pos = vol->mft_record_size;
skip_index_root:
if (!ia_na)
goto done;
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;
}
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;
while (!(bmp[bmp_buf_pos >> 3] & (1 << (bmp_buf_pos & 7)))) {
find_next_index_buffer:
bmp_pos++;
bmp_buf_pos++;
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;
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);
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 (ntfs_index_block_inconsistent((INDEX_BLOCK*)ia, index_block_size,
ia_na->ni->mft_no, ia_start >> index_vcn_size_bits)) {
goto dir_err_out;
}
index_end = (u8*)&ia->index + le32_to_cpu(ia->index.index_length);
ie = (INDEX_ENTRY*)((u8*)&ia->index +
le32_to_cpu(ia->index.entries_offset));
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));
if ((u8*)ie < (u8*)ia || (u8*)ie +
sizeof(INDEX_ENTRY_HEADER) > index_end ||
(u8*)ie + le16_to_cpu(ie->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;
}
if (ie->ie_flags & INDEX_ENTRY_END)
break;
if (!le16_to_cpu(ie->length))
goto dir_err_out;
if (ia_pos - ia_start > (u8*)ie - (u8*)ia)
continue;
if (ntfs_index_entry_inconsistent(ie, COLLATION_FILE_NAME,
dir_ni->mft_no)) {
errno = EIO;
goto dir_err_out;
}
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:
*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;
}
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;
int rollback_dir = 0;
FILE_NAME_ATTR *fn = NULL;
STANDARD_INFORMATION *si = NULL;
int err, fn_len, si_len;
ntfs_volume_special_files special_files;
ntfs_log_trace("Entering.\n");
if (!dir_ni || !name || !name_len) {
ntfs_log_error("Invalid arguments.\n");
errno = EINVAL;
return NULL;
}
ni = ntfs_mft_record_alloc(dir_ni->vol, NULL);
if (!ni)
return NULL;
#if CACHE_NIDATA_SIZE
ntfs_inode_invalidate(dir_ni->vol, ni->mft_no);
#endif
special_files = dir_ni->vol->special_files;
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)) {
switch (special_files) {
case NTFS_FILES_WSL :
if (!S_ISLNK(type)) {
si->file_attributes
= FILE_ATTRIBUTE_RECALL_ON_OPEN;
ni->flags = FILE_ATTRIBUTE_RECALL_ON_OPEN;
}
break;
default :
si->file_attributes = FILE_ATTR_SYSTEM;
ni->flags = FILE_ATTR_SYSTEM;
break;
}
}
ni->flags |= 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;
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;
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;
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;
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:
switch (special_files) {
case NTFS_FILES_WSL :
data_len = 0;
data = (INTX_FILE*)NULL;
break;
default :
data_len = offsetof(INTX_FILE,
device_end);
data = (INTX_FILE*)ntfs_malloc(
data_len);
if (!data) {
err = errno;
goto err_out;
}
data->major = cpu_to_le64(major(dev));
data->minor = cpu_to_le64(minor(dev));
if (type == S_IFBLK)
data->magic
= INTX_BLOCK_DEVICE;
if (type == S_IFCHR)
data->magic
= INTX_CHARACTER_DEVICE;
break;
}
break;
case S_IFLNK:
switch (special_files) {
case NTFS_FILES_WSL :
data_len = 0;
data = (INTX_FILE*)NULL;
break;
default :
data_len = sizeof(INTX_FILE_TYPES) +
target_len * sizeof(ntfschar);
data = (INTX_FILE*)ntfs_malloc(
data_len);
if (!data) {
err = errno;
goto err_out;
}
data->magic = INTX_SYMBOLIC_LINK;
memcpy(data->target, target,
target_len * sizeof(ntfschar));
break;
}
break;
case S_IFSOCK:
data = NULL;
if (special_files == NTFS_FILES_WSL)
data_len = 0;
else
data_len = 1;
break;
default:
data = NULL;
data_len = 0;
break;
}
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);
}
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)) {
if (special_files == NTFS_FILES_INTERIX)
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));
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;
}
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;
}
rollback_dir = 1;
ni->mrec->link_count = const_cpu_to_le16(1);
if (S_ISDIR(type))
ni->mrec->flags |= MFT_RECORD_IS_DIRECTORY;
if (special_files == NTFS_FILES_WSL) {
switch (type) {
case S_IFLNK :
err = ntfs_reparse_set_wsl_symlink(ni, target,
target_len);
break;
case S_IFIFO :
case S_IFSOCK :
case S_IFCHR :
case S_IFBLK :
err = ntfs_reparse_set_wsl_not_symlink(ni,
type);
if (!err) {
err = ntfs_ea_set_wsl_not_symlink(ni,
type, dev);
if (err)
ntfs_remove_ntfs_reparse_data(ni);
}
break;
default :
err = 0;
break;
}
if (err) {
err = errno;
goto err_out;
}
}
ntfs_inode_mark_dirty(ni);
free(fn);
free(si);
ntfs_log_trace("Done.\n");
return ni;
err_out:
ntfs_log_trace("Failed.\n");
if (rollback_dir)
ntfs_index_remove(dir_ni, ni, fn, fn_len);
if (rollback_sd)
ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0);
if (rollback_data)
ntfs_attr_remove(ni, AT_DATA, AT_UNNAMED, 0);
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;
}
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;
}
if ((na->data_size != sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY_HEADER))){
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;
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;
}
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;
actx = ntfs_attr_get_search_ctx(ni, NULL);
if (!actx)
goto err_out;
search:
while (!(err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
CASE_SENSITIVE, 0, NULL, 0, actx))) {
#ifdef DEBUG
char *s;
#endif
IGNORE_CASE_BOOL case_sensitive = IGNORE_CASE;
fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
le16_to_cpu(actx->attr->value_offset));
#ifdef DEBUG
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);
#endif
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;
}
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 (err) {
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;
if ((ni->mrec->link_count == const_cpu_to_le16(1)) && !actx->base_ntfs_ino) {
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;
}
#if CACHE_LOOKUP_SIZE
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) {
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)) {
err = errno;
}
if (ntfs_delete_object_id_index(ni)) {
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");
}
#if CACHE_NIDATA_SIZE
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;
}
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)) {
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;
}
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));
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;
}
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;
if (ntfs_index_remove(dir_ni, ni, fn, fn_len))
goto rollback_failed;
goto err_out;
}
ni->mrec->link_count = cpu_to_le16(le16_to_cpu(
ni->mrec->link_count) + 1);
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));
}
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) {
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)) {
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);
}
#define MAX_DOS_NAME_LENGTH 12
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;
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)) {
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)) {
outsize = fn->file_name_length;
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;
}
return (outsize);
}
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;
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)) {
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)) {
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 (!outsize) {
ntfs_attr_reinit_search_ctx(ctx);
while (!ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, CASE_SENSITIVE,
0, NULL, 0, ctx)) {
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)) {
outsize = fn->file_name_length;
memcpy(longname,fn->file_name,outsize*sizeof(ntfschar));
}
}
}
ntfs_attr_put_search_ctx(ctx);
return (outsize);
}
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) {
ntfs_name_upcase(dosname, doslen,
ni->vol->upcase, ni->vol->upcase_len);
outsize = ntfs_ucstombs(dosname, doslen, &outname, 0);
if (outsize < 0) {
ntfs_log_error("Cannot represent dosname in current locale.\n");
outsize = -errno;
} else {
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);
}
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);
}
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;
linkcount = le16_to_cpu(ni->mrec->link_count);
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)
&& (le16_to_cpu(ni->mrec->link_count) > linkcount)) {
if (!ntfs_delete(vol, (char*)NULL, ni, dir_ni,
deletename, deletelen)) {
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);
}
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];
if (size > 3*MAX_DOS_NAME_LENGTH)
size = 3*MAX_DOS_NAME_LENGTH;
strncpy(newname, value, size);
newname[size] = 0;
shortlen = ntfs_mbstoucs(newname, &shortname);
if (shortlen > MAX_DOS_NAME_LENGTH)
shortlen = MAX_DOS_NAME_LENGTH;
if ((shortlen < 0)
|| ntfs_forbidden_names(ni->vol,shortname,shortlen,TRUE)) {
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,
FALSE)) {
if (oldlen > 0) {
if (flags & XATTR_CREATE) {
res = -1;
errno = EEXIST;
} else
if ((shortlen == oldlen)
&& !memcmp(shortname,oldname,
oldlen*sizeof(ntfschar)))
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);
}
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) {
oldnametype = set_namespace(ni,dir_ni,longname,longlen,
FILE_NAME_POSIX);
switch (oldnametype) {
case FILE_NAME_WIN32_AND_DOS :
res = 0;
break;
case FILE_NAME_DOS :
set_namespace(ni,dir_ni,longname,longlen,
FILE_NAME_DOS);
errno = ENOENT;
break;
case FILE_NAME_WIN32 :
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 {
errno = EIO;
ntfs_log_error("Could not change"
" DOS name of inode %lld to Posix\n",
(long long)ni->mft_no);
}
break;
default :
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);
}
static int nlink_increment(void *nlink_ptr,
const ntfschar *name __attribute__((unused)),
const int len __attribute__((unused)),
const int type,
const s64 pos __attribute__((unused)),
const MFT_REF mref __attribute__((unused)),
const unsigned int dt_type)
{
if ((dt_type == NTFS_DT_DIR) && (type != FILE_NAME_DOS))
(*((int*)nlink_ptr))++;
return (0);
}
int ntfs_dir_link_cnt(ntfs_inode *ni)
{
ntfs_attr_search_ctx *actx;
FILE_NAME_ATTR *fn;
s64 pos;
int err = 0;
int nlink = 0;
if (!ni) {
ntfs_log_error("Invalid argument.\n");
errno = EINVAL;
goto err_out;
}
if (ni->nr_extents == -1)
ni = ni->base_ni;
if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
pos = 0;
err = ntfs_readdir(ni, &pos, &nlink, nlink_increment);
if (err)
nlink = 0;
} else {
actx = ntfs_attr_get_search_ctx(ni, NULL);
if (!actx)
goto err_out;
while (!(err = ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0,
CASE_SENSITIVE, 0, NULL, 0, actx))) {
fn = (FILE_NAME_ATTR*)((u8*)actx->attr +
le16_to_cpu(actx->attr->value_offset));
if (fn->file_name_type != FILE_NAME_DOS)
nlink++;
}
if (err && (errno != ENOENT))
nlink = 0;
ntfs_attr_put_search_ctx(actx);
}
if (!nlink)
ntfs_log_perror("Failed to compute nlink of inode %lld",
(long long)ni->mft_no);
err_out :
return (nlink);
}