mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-18 09:19:17 +01:00
9e79c9d99b
* code cleanup
1329 lines
41 KiB
C
1329 lines
41 KiB
C
/**
|
|
* reparse.c - Processing of reparse points
|
|
*
|
|
* This module is part of ntfs-3g library
|
|
*
|
|
* Copyright (c) 2008-2009 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_SETXATTR
|
|
#include <sys/xattr.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SYSMACROS_H
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
|
|
#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 "lcnalloc.h"
|
|
#include "logging.h"
|
|
#include "misc.h"
|
|
#include "reparse.h"
|
|
|
|
/* the definitions in layout.h are wrong, we use names defined in
|
|
http://msdn.microsoft.com/en-us/library/aa365740(VS.85).aspx
|
|
*/
|
|
|
|
#define IO_REPARSE_TAG_DFS const_cpu_to_le32(0x8000000A)
|
|
#define IO_REPARSE_TAG_DFSR const_cpu_to_le32(0x80000012)
|
|
#define IO_REPARSE_TAG_HSM const_cpu_to_le32(0xC0000004)
|
|
#define IO_REPARSE_TAG_HSM2 const_cpu_to_le32(0x80000006)
|
|
#define IO_REPARSE_TAG_MOUNT_POINT const_cpu_to_le32(0xA0000003)
|
|
#define IO_REPARSE_TAG_SIS const_cpu_to_le32(0x80000007)
|
|
#define IO_REPARSE_TAG_SYMLINK const_cpu_to_le32(0xA000000C)
|
|
|
|
struct MOUNT_POINT_REPARSE_DATA /* reparse data for junctions */
|
|
{
|
|
le16 subst_name_offset;
|
|
le16 subst_name_length;
|
|
le16 print_name_offset;
|
|
le16 print_name_length;
|
|
char path_buffer[0]; /* above data assume this is char array */
|
|
} ;
|
|
|
|
struct SYMLINK_REPARSE_DATA /* reparse data for symlinks */
|
|
{
|
|
le16 subst_name_offset;
|
|
le16 subst_name_length;
|
|
le16 print_name_offset;
|
|
le16 print_name_length;
|
|
le32 flags; /* 1 for full target, otherwise 0 */
|
|
char path_buffer[0]; /* above data assume this is char array */
|
|
} ;
|
|
|
|
struct REPARSE_INDEX /* index entry in $Extend/$Reparse */
|
|
{
|
|
INDEX_ENTRY_HEADER header;
|
|
REPARSE_INDEX_KEY key;
|
|
le32 filling;
|
|
} ;
|
|
|
|
static const ntfschar dir_junction_head[] =
|
|
{
|
|
const_cpu_to_le16( '\\' ),
|
|
const_cpu_to_le16( '?' ),
|
|
const_cpu_to_le16( '?' ),
|
|
const_cpu_to_le16( '\\' )
|
|
} ;
|
|
|
|
static const ntfschar vol_junction_head[] =
|
|
{
|
|
const_cpu_to_le16( '\\' ),
|
|
const_cpu_to_le16( '?' ),
|
|
const_cpu_to_le16( '?' ),
|
|
const_cpu_to_le16( '\\' ),
|
|
const_cpu_to_le16( 'V' ),
|
|
const_cpu_to_le16( 'o' ),
|
|
const_cpu_to_le16( 'l' ),
|
|
const_cpu_to_le16( 'u' ),
|
|
const_cpu_to_le16( 'm' ),
|
|
const_cpu_to_le16( 'e' ),
|
|
const_cpu_to_le16( '{' ),
|
|
} ;
|
|
|
|
static ntfschar reparse_index_name[] = { const_cpu_to_le16( '$' ),
|
|
const_cpu_to_le16( 'R' )
|
|
};
|
|
|
|
static const char mappingdir[] = ".NTFS-3G/";
|
|
|
|
/*
|
|
* Fix a file name with doubtful case in some directory index
|
|
* and return the name with the casing used in directory.
|
|
*
|
|
* Should only be used to translate paths stored with case insensitivity
|
|
* (such as directory junctions) when no case conflict is expected.
|
|
* If there some ambiguity, the name which collates first is returned.
|
|
*
|
|
* The name is converted to upper case and searched the usual way.
|
|
* The collation rules for file names are such that we should get the
|
|
* first candidate if any.
|
|
*/
|
|
|
|
static u64 ntfs_fix_file_name( ntfs_inode *dir_ni, ntfschar *uname,
|
|
int uname_len )
|
|
{
|
|
ntfs_volume *vol = dir_ni->vol;
|
|
ntfs_index_context *icx;
|
|
u64 mref;
|
|
le64 lemref;
|
|
int lkup;
|
|
int olderrno;
|
|
int i;
|
|
u32 cpuchar;
|
|
INDEX_ENTRY *entry;
|
|
FILE_NAME_ATTR *found;
|
|
struct
|
|
{
|
|
FILE_NAME_ATTR attr;
|
|
ntfschar file_name[NTFS_MAX_NAME_LEN + 1];
|
|
} find;
|
|
|
|
mref = ( u64 ) - 1; /* default return (not found) */
|
|
icx = ntfs_index_ctx_get( dir_ni, NTFS_INDEX_I30, 4 );
|
|
if ( icx )
|
|
{
|
|
if ( uname_len > NTFS_MAX_NAME_LEN )
|
|
uname_len = NTFS_MAX_NAME_LEN;
|
|
find.attr.file_name_length = uname_len;
|
|
for ( i = 0; i < uname_len; i++ )
|
|
{
|
|
cpuchar = le16_to_cpu( uname[i] );
|
|
/*
|
|
* We need upper or lower value, whichever is smaller,
|
|
* but we can only convert to upper case, so we
|
|
* will fail when searching for an upper case char
|
|
* whose lower case is smaller (such as umlauted Y)
|
|
*/
|
|
if ( ( cpuchar < vol->upcase_len )
|
|
&& ( le16_to_cpu( vol->upcase[cpuchar] ) < cpuchar ) )
|
|
find.attr.file_name[i] = vol->upcase[cpuchar];
|
|
else
|
|
find.attr.file_name[i] = uname[i];
|
|
}
|
|
olderrno = errno;
|
|
lkup = ntfs_index_lookup( ( char* ) & find, uname_len, icx );
|
|
if ( errno == ENOENT )
|
|
errno = olderrno;
|
|
/*
|
|
* We generally only get the first matching candidate,
|
|
* so we still have to check whether this is a real match
|
|
*/
|
|
if ( icx->entry && ( icx->entry->ie_flags & INDEX_ENTRY_END ) )
|
|
/* get next entry if reaching end of block */
|
|
entry = ntfs_index_next( icx->entry, icx );
|
|
else
|
|
entry = icx->entry;
|
|
if ( entry )
|
|
{
|
|
found = &entry->key.file_name;
|
|
if ( lkup
|
|
&& ntfs_names_are_equal( find.attr.file_name,
|
|
find.attr.file_name_length,
|
|
found->file_name, found->file_name_length,
|
|
IGNORE_CASE,
|
|
vol->upcase, vol->upcase_len ) )
|
|
lkup = 0;
|
|
if ( !lkup )
|
|
{
|
|
/*
|
|
* name found :
|
|
* fix original name and return inode
|
|
*/
|
|
lemref = entry->indexed_file;
|
|
mref = le64_to_cpu( lemref );
|
|
for ( i = 0; i < found->file_name_length; i++ )
|
|
uname[i] = found->file_name[i];
|
|
}
|
|
}
|
|
ntfs_index_ctx_put( icx );
|
|
}
|
|
return ( mref );
|
|
}
|
|
|
|
/*
|
|
* Search for a directory junction or a symbolic link
|
|
* along the target path, with target defined as a full absolute path
|
|
*
|
|
* Returns the path translated to a Linux path
|
|
* or NULL if the path is not valid
|
|
*/
|
|
|
|
static char *search_absolute( ntfs_volume *vol, ntfschar *path,
|
|
int count, BOOL isdir )
|
|
{
|
|
ntfs_inode *ni;
|
|
u64 inum;
|
|
char *target;
|
|
int start;
|
|
int len;
|
|
|
|
target = ( char* )NULL; /* default return */
|
|
ni = ntfs_inode_open( vol, ( MFT_REF )FILE_root );
|
|
if ( ni )
|
|
{
|
|
start = 0;
|
|
do
|
|
{
|
|
len = 0;
|
|
while ( ( ( start + len ) < count )
|
|
&& ( path[start + len] != const_cpu_to_le16( '\\' ) ) )
|
|
len++;
|
|
inum = ntfs_fix_file_name( ni, &path[start], len );
|
|
ntfs_inode_close( ni );
|
|
ni = ( ntfs_inode* )NULL;
|
|
if ( inum != ( u64 ) - 1 )
|
|
{
|
|
inum = MREF( inum );
|
|
ni = ntfs_inode_open( vol, inum );
|
|
start += len;
|
|
if ( start < count )
|
|
path[start++] = const_cpu_to_le16( '/' );
|
|
}
|
|
}
|
|
while ( ni
|
|
&& ( ni->mrec->flags & MFT_RECORD_IS_DIRECTORY )
|
|
&& ( start < count ) );
|
|
if ( ni
|
|
&& ( ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ? isdir : !isdir ) )
|
|
if ( ntfs_ucstombs( path, count, &target, 0 ) < 0 )
|
|
{
|
|
if ( target )
|
|
{
|
|
free( target );
|
|
target = ( char* )NULL;
|
|
}
|
|
}
|
|
if ( ni )
|
|
ntfs_inode_close( ni );
|
|
}
|
|
return ( target );
|
|
}
|
|
|
|
/*
|
|
* Search for a symbolic link along the target path,
|
|
* with the target defined as a relative path
|
|
*
|
|
* Note : the path used to access the current inode, may be
|
|
* different from the one implied in the target definition,
|
|
* when an inode has names in several directories.
|
|
*
|
|
* Returns the path translated to a Linux path
|
|
* or NULL if the path is not valid
|
|
*/
|
|
|
|
static char *search_relative( ntfs_inode *ni, ntfschar *path, int count )
|
|
{
|
|
char *target = ( char* )NULL;
|
|
ntfs_inode *curni;
|
|
ntfs_inode *newni;
|
|
u64 inum;
|
|
int pos;
|
|
int lth;
|
|
BOOL ok;
|
|
int max = 32; /* safety */
|
|
|
|
pos = 0;
|
|
ok = TRUE;
|
|
curni = ntfs_dir_parent_inode( ni );
|
|
while ( curni && ok && ( pos < ( count - 1 ) ) && --max )
|
|
{
|
|
if ( ( count >= ( pos + 2 ) )
|
|
&& ( path[pos] == const_cpu_to_le16( '.' ) )
|
|
&& ( path[pos+1] == const_cpu_to_le16( '\\' ) ) )
|
|
{
|
|
path[1] = const_cpu_to_le16( '/' );
|
|
pos += 2;
|
|
}
|
|
else
|
|
{
|
|
if ( ( count >= ( pos + 3 ) )
|
|
&& ( path[pos] == const_cpu_to_le16( '.' ) )
|
|
&& ( path[pos+1] == const_cpu_to_le16( '.' ) )
|
|
&& ( path[pos+2] == const_cpu_to_le16( '\\' ) ) )
|
|
{
|
|
path[2] = const_cpu_to_le16( '/' );
|
|
pos += 3;
|
|
newni = ntfs_dir_parent_inode( curni );
|
|
if ( curni != ni )
|
|
ntfs_inode_close( curni );
|
|
curni = newni;
|
|
if ( !curni )
|
|
ok = FALSE;
|
|
}
|
|
else
|
|
{
|
|
lth = 0;
|
|
while ( ( ( pos + lth ) < count )
|
|
&& ( path[pos + lth] != const_cpu_to_le16( '\\' ) ) )
|
|
lth++;
|
|
if ( lth > 0 )
|
|
inum = ntfs_fix_file_name( curni, &path[pos], lth );
|
|
else
|
|
inum = ( u64 ) - 1;
|
|
if ( !lth
|
|
|| ( ( curni != ni )
|
|
&& ntfs_inode_close( curni ) )
|
|
|| ( inum == ( u64 ) - 1 ) )
|
|
ok = FALSE;
|
|
else
|
|
{
|
|
curni = ntfs_inode_open( ni->vol, MREF( inum ) );
|
|
if ( !curni )
|
|
ok = FALSE;
|
|
else
|
|
{
|
|
if ( ok && ( ( pos + lth ) < count ) )
|
|
{
|
|
path[pos + lth] = const_cpu_to_le16( '/' );
|
|
pos += lth + 1;
|
|
}
|
|
else
|
|
{
|
|
pos += lth;
|
|
if ( ( ni->mrec->flags ^ curni->mrec->flags )
|
|
& MFT_RECORD_IS_DIRECTORY )
|
|
ok = FALSE;
|
|
if ( ntfs_inode_close( curni ) )
|
|
ok = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ok && ( ntfs_ucstombs( path, count, &target, 0 ) < 0 ) )
|
|
{
|
|
free( target ); // needed ?
|
|
target = ( char* )NULL;
|
|
}
|
|
return ( target );
|
|
}
|
|
|
|
/*
|
|
* Check whether a drive letter has been defined in .NTFS-3G
|
|
*
|
|
* Returns 1 if found,
|
|
* 0 if not found,
|
|
* -1 if there was an error (described by errno)
|
|
*/
|
|
|
|
static int ntfs_drive_letter( ntfs_volume *vol, ntfschar letter )
|
|
{
|
|
char defines[NTFS_MAX_NAME_LEN + 5];
|
|
char *drive;
|
|
int ret;
|
|
int sz;
|
|
int olderrno;
|
|
ntfs_inode *ni;
|
|
|
|
ret = -1;
|
|
drive = ( char* )NULL;
|
|
sz = ntfs_ucstombs( &letter, 1, &drive, 0 );
|
|
if ( sz > 0 )
|
|
{
|
|
strcpy( defines, mappingdir );
|
|
if ( ( *drive >= 'a' ) && ( *drive <= 'z' ) )
|
|
*drive += 'A' - 'a';
|
|
strcat( defines, drive );
|
|
strcat( defines, ":" );
|
|
olderrno = errno;
|
|
ni = ntfs_pathname_to_inode( vol, NULL, defines );
|
|
if ( ni && !ntfs_inode_close( ni ) )
|
|
ret = 1;
|
|
else if ( errno == ENOENT )
|
|
{
|
|
ret = 0;
|
|
/* avoid errno pollution */
|
|
errno = olderrno;
|
|
}
|
|
}
|
|
if ( drive )
|
|
free( drive );
|
|
return ( ret );
|
|
}
|
|
|
|
/*
|
|
* Do some sanity checks on reparse data
|
|
*
|
|
* The only general check is about the size (at least the tag must
|
|
* be present)
|
|
* If the reparse data looks like a junction point or symbolic
|
|
* link, more checks can be done.
|
|
*
|
|
*/
|
|
|
|
static BOOL valid_reparse_data( ntfs_inode *ni,
|
|
const REPARSE_POINT *reparse_attr, size_t size )
|
|
{
|
|
BOOL ok;
|
|
unsigned int offs;
|
|
unsigned int lth;
|
|
const struct MOUNT_POINT_REPARSE_DATA *mount_point_data;
|
|
const struct SYMLINK_REPARSE_DATA *symlink_data;
|
|
|
|
ok = ni && reparse_attr
|
|
&& ( size >= sizeof( REPARSE_POINT ) )
|
|
&& ( ( ( size_t )le16_to_cpu( reparse_attr->reparse_data_length )
|
|
+ sizeof( REPARSE_POINT ) ) == size );
|
|
if ( ok )
|
|
{
|
|
switch ( reparse_attr->reparse_tag )
|
|
{
|
|
case IO_REPARSE_TAG_MOUNT_POINT :
|
|
mount_point_data = ( const struct MOUNT_POINT_REPARSE_DATA* )
|
|
reparse_attr->reparse_data;
|
|
offs = le16_to_cpu( mount_point_data->subst_name_offset );
|
|
lth = le16_to_cpu( mount_point_data->subst_name_length );
|
|
/* consistency checks */
|
|
if ( !( ni->mrec->flags & MFT_RECORD_IS_DIRECTORY )
|
|
|| ( ( size_t )( ( sizeof( REPARSE_POINT )
|
|
+ sizeof( struct MOUNT_POINT_REPARSE_DATA )
|
|
+ offs + lth ) ) > size ) )
|
|
ok = FALSE;
|
|
break;
|
|
case IO_REPARSE_TAG_SYMLINK :
|
|
symlink_data = ( const struct SYMLINK_REPARSE_DATA* )
|
|
reparse_attr->reparse_data;
|
|
offs = le16_to_cpu( symlink_data->subst_name_offset );
|
|
lth = le16_to_cpu( symlink_data->subst_name_length );
|
|
if ( ( size_t )( ( sizeof( REPARSE_POINT )
|
|
+ sizeof( struct SYMLINK_REPARSE_DATA )
|
|
+ offs + lth ) ) > size )
|
|
ok = FALSE;
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
}
|
|
if ( !ok )
|
|
errno = EINVAL;
|
|
return ( ok );
|
|
}
|
|
|
|
/*
|
|
* Check and translate the target of a junction point or
|
|
* a full absolute symbolic link.
|
|
*
|
|
* A full target definition begins with "\??\" or "\\?\"
|
|
*
|
|
* The fully defined target is redefined as a relative link,
|
|
* - either to the target if found on the same device.
|
|
* - or into the /.NTFS-3G directory for the user to define
|
|
* In the first situation, the target is translated to case-sensitive path.
|
|
*
|
|
* returns the target converted to a relative symlink
|
|
* or NULL if there were some problem, as described by errno
|
|
*/
|
|
|
|
static char *ntfs_get_fulllink( ntfs_volume *vol, ntfschar *junction,
|
|
int count, const char *mnt_point, BOOL isdir )
|
|
{
|
|
char *target;
|
|
char *fulltarget;
|
|
int sz;
|
|
char *q;
|
|
enum { DIR_JUNCTION, VOL_JUNCTION, NO_JUNCTION } kind;
|
|
|
|
target = ( char* )NULL;
|
|
fulltarget = ( char* )NULL;
|
|
/*
|
|
* For a valid directory junction we want \??\x:\
|
|
* where \ is an individual char and x a non-null char
|
|
*/
|
|
if ( ( count >= 7 )
|
|
&& !memcmp( junction, dir_junction_head, 8 )
|
|
&& junction[4]
|
|
&& ( junction[5] == const_cpu_to_le16( ':' ) )
|
|
&& ( junction[6] == const_cpu_to_le16( '\\' ) ) )
|
|
kind = DIR_JUNCTION;
|
|
else
|
|
/*
|
|
* For a valid volume junction we want \\?\Volume{
|
|
* and a final \ (where \ is an individual char)
|
|
*/
|
|
if ( ( count >= 12 )
|
|
&& !memcmp( junction, vol_junction_head, 22 )
|
|
&& ( junction[count-1] == const_cpu_to_le16( '\\' ) ) )
|
|
kind = VOL_JUNCTION;
|
|
else
|
|
kind = NO_JUNCTION;
|
|
/*
|
|
* Directory junction with an explicit path and
|
|
* no specific definition for the drive letter :
|
|
* try to interpret as a target on the same volume
|
|
*/
|
|
if ( ( kind == DIR_JUNCTION )
|
|
&& ( count >= 7 )
|
|
&& junction[7]
|
|
&& !ntfs_drive_letter( vol, junction[4] ) )
|
|
{
|
|
target = search_absolute( vol, &junction[7], count - 7, isdir );
|
|
if ( target )
|
|
{
|
|
fulltarget = ( char* )ntfs_malloc( strlen( mnt_point )
|
|
+ strlen( target ) + 2 );
|
|
if ( fulltarget )
|
|
{
|
|
strcpy( fulltarget, mnt_point );
|
|
strcat( fulltarget, "/" );
|
|
strcat( fulltarget, target );
|
|
}
|
|
free( target );
|
|
}
|
|
}
|
|
/*
|
|
* Volume junctions or directory junctions with
|
|
* target not found on current volume :
|
|
* link to /.NTFS-3G/target which the user can
|
|
* define as a symbolic link to the real target
|
|
*/
|
|
if ( ( ( kind == DIR_JUNCTION ) && !fulltarget )
|
|
|| ( kind == VOL_JUNCTION ) )
|
|
{
|
|
sz = ntfs_ucstombs( &junction[4],
|
|
( kind == VOL_JUNCTION ? count - 5 : count - 4 ),
|
|
&target, 0 );
|
|
if ( ( sz > 0 ) && target )
|
|
{
|
|
/* reverse slashes */
|
|
for ( q = target; *q; q++ )
|
|
if ( *q == '\\' )
|
|
*q = '/';
|
|
/* force uppercase drive letter */
|
|
if ( ( target[1] == ':' )
|
|
&& ( target[0] >= 'a' )
|
|
&& ( target[0] <= 'z' ) )
|
|
target[0] += 'A' - 'a';
|
|
fulltarget = ( char* )ntfs_malloc( strlen( mnt_point )
|
|
+ sizeof( mappingdir ) + strlen( target ) + 1 );
|
|
if ( fulltarget )
|
|
{
|
|
strcpy( fulltarget, mnt_point );
|
|
strcat( fulltarget, "/" );
|
|
strcat( fulltarget, mappingdir );
|
|
strcat( fulltarget, target );
|
|
}
|
|
}
|
|
if ( target )
|
|
free( target );
|
|
}
|
|
return ( fulltarget );
|
|
}
|
|
|
|
/*
|
|
* Check and translate the target of an absolute symbolic link.
|
|
*
|
|
* An absolute target definition begins with "\" or "x:\"
|
|
*
|
|
* The absolute target is redefined as a relative link,
|
|
* - either to the target if found on the same device.
|
|
* - or into the /.NTFS-3G directory for the user to define
|
|
* In the first situation, the target is translated to case-sensitive path.
|
|
*
|
|
* returns the target converted to a relative symlink
|
|
* or NULL if there were some problem, as described by errno
|
|
*/
|
|
|
|
static char *ntfs_get_abslink( ntfs_volume *vol, ntfschar *junction,
|
|
int count, const char *mnt_point, BOOL isdir )
|
|
{
|
|
char *target;
|
|
char *fulltarget;
|
|
int sz;
|
|
char *q;
|
|
enum { FULL_PATH, ABS_PATH, REJECTED_PATH } kind;
|
|
|
|
target = ( char* )NULL;
|
|
fulltarget = ( char* )NULL;
|
|
/*
|
|
* For a full valid path we want x:\
|
|
* where \ is an individual char and x a non-null char
|
|
*/
|
|
if ( ( count >= 3 )
|
|
&& junction[0]
|
|
&& ( junction[1] == const_cpu_to_le16( ':' ) )
|
|
&& ( junction[2] == const_cpu_to_le16( '\\' ) ) )
|
|
kind = FULL_PATH;
|
|
else
|
|
/*
|
|
* For an absolute path we want an initial \
|
|
*/
|
|
if ( ( count >= 0 )
|
|
&& ( junction[0] == const_cpu_to_le16( '\\' ) ) )
|
|
kind = ABS_PATH;
|
|
else
|
|
kind = REJECTED_PATH;
|
|
/*
|
|
* Full path, with a drive letter and
|
|
* no specific definition for the drive letter :
|
|
* try to interpret as a target on the same volume.
|
|
* Do the same for an abs path with no drive letter.
|
|
*/
|
|
if ( ( ( kind == FULL_PATH )
|
|
&& ( count >= 3 )
|
|
&& junction[3]
|
|
&& !ntfs_drive_letter( vol, junction[0] ) )
|
|
|| ( kind == ABS_PATH ) )
|
|
{
|
|
if ( kind == ABS_PATH )
|
|
target = search_absolute( vol, &junction[1],
|
|
count - 1, isdir );
|
|
else
|
|
target = search_absolute( vol, &junction[3],
|
|
count - 3, isdir );
|
|
if ( target )
|
|
{
|
|
fulltarget = ( char* )ntfs_malloc( strlen( mnt_point )
|
|
+ strlen( target ) + 2 );
|
|
if ( fulltarget )
|
|
{
|
|
strcpy( fulltarget, mnt_point );
|
|
strcat( fulltarget, "/" );
|
|
strcat( fulltarget, target );
|
|
}
|
|
free( target );
|
|
}
|
|
}
|
|
/*
|
|
* full path with target not found on current volume :
|
|
* link to /.NTFS-3G/target which the user can
|
|
* define as a symbolic link to the real target
|
|
*/
|
|
if ( ( kind == FULL_PATH ) && !fulltarget )
|
|
{
|
|
sz = ntfs_ucstombs( &junction[0],
|
|
count, &target, 0 );
|
|
if ( ( sz > 0 ) && target )
|
|
{
|
|
/* reverse slashes */
|
|
for ( q = target; *q; q++ )
|
|
if ( *q == '\\' )
|
|
*q = '/';
|
|
/* force uppercase drive letter */
|
|
if ( ( target[1] == ':' )
|
|
&& ( target[0] >= 'a' )
|
|
&& ( target[0] <= 'z' ) )
|
|
target[0] += 'A' - 'a';
|
|
fulltarget = ( char* )ntfs_malloc( strlen( mnt_point )
|
|
+ sizeof( mappingdir ) + strlen( target ) + 1 );
|
|
if ( fulltarget )
|
|
{
|
|
strcpy( fulltarget, mnt_point );
|
|
strcat( fulltarget, "/" );
|
|
strcat( fulltarget, mappingdir );
|
|
strcat( fulltarget, target );
|
|
}
|
|
}
|
|
if ( target )
|
|
free( target );
|
|
}
|
|
return ( fulltarget );
|
|
}
|
|
|
|
/*
|
|
* Check and translate the target of a relative symbolic link.
|
|
*
|
|
* A relative target definition does not begin with "\"
|
|
*
|
|
* The original definition of relative target is kept, it is just
|
|
* translated to a case-sensitive path.
|
|
*
|
|
* returns the target converted to a relative symlink
|
|
* or NULL if there were some problem, as described by errno
|
|
*/
|
|
|
|
static char *ntfs_get_rellink( ntfs_inode *ni, ntfschar *junction, int count )
|
|
{
|
|
char *target;
|
|
|
|
target = search_relative( ni, junction, count );
|
|
return ( target );
|
|
}
|
|
|
|
/*
|
|
* Get the target for a junction point or symbolic link
|
|
* Should only be called for files or directories with reparse data
|
|
*
|
|
* returns the target converted to a relative path, or NULL
|
|
* if some error occurred, as described by errno
|
|
* errno is EOPNOTSUPP if the reparse point is not a valid
|
|
* symbolic link or directory junction
|
|
*/
|
|
|
|
char *ntfs_make_symlink( ntfs_inode *ni, const char *mnt_point,
|
|
int *pattr_size )
|
|
{
|
|
s64 attr_size = 0;
|
|
char *target;
|
|
unsigned int offs;
|
|
unsigned int lth;
|
|
ntfs_volume *vol;
|
|
REPARSE_POINT *reparse_attr;
|
|
struct MOUNT_POINT_REPARSE_DATA *mount_point_data;
|
|
struct SYMLINK_REPARSE_DATA *symlink_data;
|
|
enum { FULL_TARGET, ABS_TARGET, REL_TARGET } kind;
|
|
ntfschar *p;
|
|
BOOL bad;
|
|
BOOL isdir;
|
|
|
|
target = ( char* )NULL;
|
|
bad = TRUE;
|
|
isdir = ( ni->mrec->flags & MFT_RECORD_IS_DIRECTORY )
|
|
!= const_cpu_to_le16( 0 );
|
|
vol = ni->vol;
|
|
reparse_attr = ( REPARSE_POINT* )ntfs_attr_readall( ni,
|
|
AT_REPARSE_POINT, ( ntfschar* )NULL, 0, &attr_size );
|
|
if ( reparse_attr && attr_size
|
|
&& valid_reparse_data( ni, reparse_attr, attr_size ) )
|
|
{
|
|
switch ( reparse_attr->reparse_tag )
|
|
{
|
|
case IO_REPARSE_TAG_MOUNT_POINT :
|
|
mount_point_data = ( struct MOUNT_POINT_REPARSE_DATA* )
|
|
reparse_attr->reparse_data;
|
|
offs = le16_to_cpu( mount_point_data->subst_name_offset );
|
|
lth = le16_to_cpu( mount_point_data->subst_name_length );
|
|
/* reparse data consistency has been checked */
|
|
target = ntfs_get_fulllink( vol,
|
|
( ntfschar* ) & mount_point_data->path_buffer[offs],
|
|
lth / 2, mnt_point, isdir );
|
|
if ( target )
|
|
bad = FALSE;
|
|
break;
|
|
case IO_REPARSE_TAG_SYMLINK :
|
|
symlink_data = ( struct SYMLINK_REPARSE_DATA* )
|
|
reparse_attr->reparse_data;
|
|
offs = le16_to_cpu( symlink_data->subst_name_offset );
|
|
lth = le16_to_cpu( symlink_data->subst_name_length );
|
|
p = ( ntfschar* ) & symlink_data->path_buffer[offs];
|
|
/*
|
|
* Predetermine the kind of target,
|
|
* the called function has to make a full check
|
|
*/
|
|
if ( *p++ == const_cpu_to_le16( '\\' ) )
|
|
{
|
|
if ( ( *p == const_cpu_to_le16( '?' ) )
|
|
|| ( *p == const_cpu_to_le16( '\\' ) ) )
|
|
kind = FULL_TARGET;
|
|
else
|
|
kind = ABS_TARGET;
|
|
}
|
|
else if ( *p == const_cpu_to_le16( ':' ) )
|
|
kind = ABS_TARGET;
|
|
else
|
|
kind = REL_TARGET;
|
|
p--;
|
|
/* reparse data consistency has been checked */
|
|
switch ( kind )
|
|
{
|
|
case FULL_TARGET :
|
|
if ( !( symlink_data->flags
|
|
& const_cpu_to_le32( 1 ) ) )
|
|
{
|
|
target = ntfs_get_fulllink( vol,
|
|
p, lth / 2,
|
|
mnt_point, isdir );
|
|
if ( target )
|
|
bad = FALSE;
|
|
}
|
|
break;
|
|
case ABS_TARGET :
|
|
if ( symlink_data->flags
|
|
& const_cpu_to_le32( 1 ) )
|
|
{
|
|
target = ntfs_get_abslink( vol,
|
|
p, lth / 2,
|
|
mnt_point, isdir );
|
|
if ( target )
|
|
bad = FALSE;
|
|
}
|
|
break;
|
|
case REL_TARGET :
|
|
if ( symlink_data->flags
|
|
& const_cpu_to_le32( 1 ) )
|
|
{
|
|
target = ntfs_get_rellink( ni,
|
|
p, lth / 2 );
|
|
if ( target )
|
|
bad = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
free( reparse_attr );
|
|
}
|
|
*pattr_size = attr_size;
|
|
if ( bad )
|
|
errno = EOPNOTSUPP;
|
|
return ( target );
|
|
}
|
|
|
|
/*
|
|
* Check whether a reparse point looks like a junction point
|
|
* or a symbolic link.
|
|
* Should only be called for files or directories with reparse data
|
|
*
|
|
* The validity of the target is not checked.
|
|
*/
|
|
|
|
BOOL ntfs_possible_symlink( ntfs_inode *ni )
|
|
{
|
|
s64 attr_size = 0;
|
|
REPARSE_POINT *reparse_attr;
|
|
BOOL possible;
|
|
|
|
possible = FALSE;
|
|
reparse_attr = ( REPARSE_POINT* )ntfs_attr_readall( ni,
|
|
AT_REPARSE_POINT, ( ntfschar* )NULL, 0, &attr_size );
|
|
if ( reparse_attr && attr_size )
|
|
{
|
|
switch ( reparse_attr->reparse_tag )
|
|
{
|
|
case IO_REPARSE_TAG_MOUNT_POINT :
|
|
case IO_REPARSE_TAG_SYMLINK :
|
|
possible = TRUE;
|
|
default : ;
|
|
}
|
|
free( reparse_attr );
|
|
}
|
|
return ( possible );
|
|
}
|
|
|
|
#ifdef HAVE_SETXATTR /* extended attributes interface required */
|
|
|
|
/*
|
|
* Set the index for new reparse data
|
|
*
|
|
* Returns 0 if success
|
|
* -1 if failure, explained by errno
|
|
*/
|
|
|
|
static int set_reparse_index( ntfs_inode *ni, ntfs_index_context *xr,
|
|
le32 reparse_tag )
|
|
{
|
|
struct REPARSE_INDEX indx;
|
|
u64 file_id_cpu;
|
|
le64 file_id;
|
|
le16 seqn;
|
|
|
|
seqn = ni->mrec->sequence_number;
|
|
file_id_cpu = MK_MREF( ni->mft_no, le16_to_cpu( seqn ) );
|
|
file_id = cpu_to_le64( file_id_cpu );
|
|
indx.header.data_offset = const_cpu_to_le16(
|
|
sizeof( INDEX_ENTRY_HEADER )
|
|
+ sizeof( REPARSE_INDEX_KEY ) );
|
|
indx.header.data_length = const_cpu_to_le16( 0 );
|
|
indx.header.reservedV = const_cpu_to_le32( 0 );
|
|
indx.header.length = const_cpu_to_le16(
|
|
sizeof( struct REPARSE_INDEX ) );
|
|
indx.header.key_length = const_cpu_to_le16(
|
|
sizeof( REPARSE_INDEX_KEY ) );
|
|
indx.header.flags = const_cpu_to_le16( 0 );
|
|
indx.header.reserved = const_cpu_to_le16( 0 );
|
|
indx.key.reparse_tag = reparse_tag;
|
|
/* danger on processors which require proper alignment ! */
|
|
memcpy( &indx.key.file_id, &file_id, 8 );
|
|
indx.filling = const_cpu_to_le32( 0 );
|
|
ntfs_index_ctx_reinit( xr );
|
|
return ( ntfs_ie_add( xr, ( INDEX_ENTRY* )&indx ) );
|
|
}
|
|
|
|
#endif /* HAVE_SETXATTR */
|
|
|
|
/*
|
|
* Remove a reparse data index entry if attribute present
|
|
*
|
|
* Returns the size of existing reparse data
|
|
* (the existing reparse tag is returned)
|
|
* -1 if failure, explained by errno
|
|
*/
|
|
|
|
static int remove_reparse_index( ntfs_attr *na, ntfs_index_context *xr,
|
|
le32 *preparse_tag )
|
|
{
|
|
REPARSE_INDEX_KEY key;
|
|
u64 file_id_cpu;
|
|
le64 file_id;
|
|
s64 size;
|
|
le16 seqn;
|
|
int ret;
|
|
|
|
ret = na->data_size;
|
|
if ( ret )
|
|
{
|
|
/* read the existing reparse_tag */
|
|
size = ntfs_attr_pread( na, 0, 4, preparse_tag );
|
|
if ( size == 4 )
|
|
{
|
|
seqn = na->ni->mrec->sequence_number;
|
|
file_id_cpu = MK_MREF( na->ni->mft_no, le16_to_cpu( seqn ) );
|
|
file_id = cpu_to_le64( file_id_cpu );
|
|
key.reparse_tag = *preparse_tag;
|
|
/* danger on processors which require proper alignment ! */
|
|
memcpy( &key.file_id, &file_id, 8 );
|
|
if ( !ntfs_index_lookup( &key, sizeof( REPARSE_INDEX_KEY ), xr )
|
|
&& ntfs_index_rm( xr ) )
|
|
ret = -1;
|
|
}
|
|
else
|
|
{
|
|
ret = -1;
|
|
errno = ENODATA;
|
|
}
|
|
}
|
|
return ( ret );
|
|
}
|
|
|
|
/*
|
|
* Open the $Extend/$Reparse file and its index
|
|
*
|
|
* Return the index context if opened
|
|
* or NULL if an error occurred (errno tells why)
|
|
*
|
|
* The index has to be freed and inode closed when not needed any more.
|
|
*/
|
|
|
|
static ntfs_index_context *open_reparse_index( ntfs_volume *vol )
|
|
{
|
|
u64 inum;
|
|
ntfs_inode *ni;
|
|
ntfs_inode *dir_ni;
|
|
ntfs_index_context *xr;
|
|
|
|
/* do not use path_name_to inode - could reopen root */
|
|
dir_ni = ntfs_inode_open( vol, FILE_Extend );
|
|
ni = ( ntfs_inode* )NULL;
|
|
if ( dir_ni )
|
|
{
|
|
inum = ntfs_inode_lookup_by_mbsname( dir_ni, "$Reparse" );
|
|
if ( inum != ( u64 ) - 1 )
|
|
ni = ntfs_inode_open( vol, inum );
|
|
ntfs_inode_close( dir_ni );
|
|
}
|
|
if ( ni )
|
|
{
|
|
xr = ntfs_index_ctx_get( ni, reparse_index_name, 2 );
|
|
if ( !xr )
|
|
{
|
|
ntfs_inode_close( ni );
|
|
}
|
|
}
|
|
else
|
|
xr = ( ntfs_index_context* )NULL;
|
|
return ( xr );
|
|
}
|
|
|
|
#ifdef HAVE_SETXATTR /* extended attributes interface required */
|
|
|
|
/*
|
|
* Update the reparse data and index
|
|
*
|
|
* The reparse data attribute should have been created, and
|
|
* an existing index is expected if there is an existing value.
|
|
*
|
|
* Returns 0 if success
|
|
* -1 if failure, explained by errno
|
|
* If could not remove the existing index, nothing is done,
|
|
* If could not write the new data, no index entry is inserted
|
|
* If failed to insert the index, data is removed
|
|
*/
|
|
|
|
static int update_reparse_data( ntfs_inode *ni, ntfs_index_context *xr,
|
|
const char *value, size_t size )
|
|
{
|
|
int res;
|
|
int written;
|
|
int oldsize;
|
|
ntfs_attr *na;
|
|
le32 reparse_tag;
|
|
|
|
res = 0;
|
|
na = ntfs_attr_open( ni, AT_REPARSE_POINT, AT_UNNAMED, 0 );
|
|
if ( na )
|
|
{
|
|
/* remove the existing reparse data */
|
|
oldsize = remove_reparse_index( na, xr, &reparse_tag );
|
|
if ( oldsize < 0 )
|
|
res = -1;
|
|
else
|
|
{
|
|
/* resize attribute */
|
|
res = ntfs_attr_truncate( na, ( s64 )size );
|
|
/* overwrite value if any */
|
|
if ( !res && value )
|
|
{
|
|
written = ( int )ntfs_attr_pwrite( na,
|
|
( s64 )0, ( s64 )size, value );
|
|
if ( written != ( s64 )size )
|
|
{
|
|
ntfs_log_error( "Failed to update "
|
|
"reparse data\n" );
|
|
errno = EIO;
|
|
res = -1;
|
|
}
|
|
}
|
|
if ( !res
|
|
&& set_reparse_index( ni, xr,
|
|
( ( const REPARSE_POINT* )value )->reparse_tag )
|
|
&& ( oldsize > 0 ) )
|
|
{
|
|
/*
|
|
* If cannot index, try to remove the reparse
|
|
* data and log the error. There will be an
|
|
* inconsistency if removal fails.
|
|
*/
|
|
ntfs_attr_rm( na );
|
|
ntfs_log_error( "Failed to index reparse data."
|
|
" Possible corruption.\n" );
|
|
}
|
|
}
|
|
ntfs_attr_close( na );
|
|
NInoSetDirty( ni );
|
|
}
|
|
else
|
|
res = -1;
|
|
return ( res );
|
|
}
|
|
|
|
#endif /* HAVE_SETXATTR */
|
|
|
|
/*
|
|
* Delete a reparse index entry
|
|
*
|
|
* Returns 0 if success
|
|
* -1 if failure, explained by errno
|
|
*/
|
|
|
|
int ntfs_delete_reparse_index( ntfs_inode *ni )
|
|
{
|
|
ntfs_index_context *xr;
|
|
ntfs_inode *xrni;
|
|
ntfs_attr *na;
|
|
le32 reparse_tag;
|
|
int res;
|
|
|
|
res = 0;
|
|
na = ntfs_attr_open( ni, AT_REPARSE_POINT, AT_UNNAMED, 0 );
|
|
if ( na )
|
|
{
|
|
/*
|
|
* read the existing reparse data (the tag is enough)
|
|
* and un-index it
|
|
*/
|
|
xr = open_reparse_index( ni->vol );
|
|
if ( xr )
|
|
{
|
|
if ( remove_reparse_index( na, xr, &reparse_tag ) < 0 )
|
|
res = -1;
|
|
xrni = xr->ni;
|
|
ntfs_index_entry_mark_dirty( xr );
|
|
NInoSetDirty( xrni );
|
|
ntfs_index_ctx_put( xr );
|
|
ntfs_inode_close( xrni );
|
|
}
|
|
ntfs_attr_close( na );
|
|
}
|
|
return ( res );
|
|
}
|
|
|
|
#ifdef HAVE_SETXATTR /* extended attributes interface required */
|
|
|
|
/*
|
|
* Get the ntfs reparse data into an extended attribute
|
|
*
|
|
* Returns the reparse data size
|
|
* and the buffer is updated if it is long enough
|
|
*/
|
|
|
|
int ntfs_get_ntfs_reparse_data( ntfs_inode *ni, char *value, size_t size )
|
|
{
|
|
REPARSE_POINT *reparse_attr;
|
|
s64 attr_size;
|
|
|
|
attr_size = 0; /* default to no data and no error */
|
|
if ( ni )
|
|
{
|
|
if ( ni->flags & FILE_ATTR_REPARSE_POINT )
|
|
{
|
|
reparse_attr = ( REPARSE_POINT* )ntfs_attr_readall( ni,
|
|
AT_REPARSE_POINT, ( ntfschar* )NULL, 0, &attr_size );
|
|
if ( reparse_attr )
|
|
{
|
|
if ( attr_size <= ( s64 )size )
|
|
{
|
|
if ( value )
|
|
memcpy( value, reparse_attr,
|
|
attr_size );
|
|
else
|
|
errno = EINVAL;
|
|
}
|
|
free( reparse_attr );
|
|
}
|
|
}
|
|
else
|
|
errno = ENODATA;
|
|
}
|
|
return ( attr_size ? ( int )attr_size : -errno );
|
|
}
|
|
|
|
/*
|
|
* Set the reparse data from an extended attribute
|
|
*
|
|
* Warning : the new data is not checked
|
|
*
|
|
* Returns 0, or -1 if there is a problem
|
|
*/
|
|
|
|
int ntfs_set_ntfs_reparse_data( ntfs_inode *ni,
|
|
const char *value, size_t size, int flags )
|
|
{
|
|
int res;
|
|
u8 dummy;
|
|
ntfs_inode *xrni;
|
|
ntfs_index_context *xr;
|
|
|
|
res = 0;
|
|
if ( ni && valid_reparse_data( ni, ( const REPARSE_POINT* )value, size ) )
|
|
{
|
|
xr = open_reparse_index( ni->vol );
|
|
if ( xr )
|
|
{
|
|
if ( !ntfs_attr_exist( ni, AT_REPARSE_POINT,
|
|
AT_UNNAMED, 0 ) )
|
|
{
|
|
if ( !( flags & XATTR_REPLACE ) )
|
|
{
|
|
/*
|
|
* no reparse data attribute : add one,
|
|
* apparently, this does not feed the new value in
|
|
* Note : NTFS version must be >= 3
|
|
*/
|
|
if ( ni->vol->major_ver >= 3 )
|
|
{
|
|
res = ntfs_attr_add( ni,
|
|
AT_REPARSE_POINT,
|
|
AT_UNNAMED, 0, &dummy,
|
|
( s64 )0 );
|
|
if ( !res )
|
|
{
|
|
ni->flags |=
|
|
FILE_ATTR_REPARSE_POINT;
|
|
NInoFileNameSetDirty( ni );
|
|
}
|
|
NInoSetDirty( ni );
|
|
}
|
|
else
|
|
{
|
|
errno = EOPNOTSUPP;
|
|
res = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
errno = ENODATA;
|
|
res = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( flags & XATTR_CREATE )
|
|
{
|
|
errno = EEXIST;
|
|
res = -1;
|
|
}
|
|
}
|
|
if ( !res )
|
|
{
|
|
/* update value and index */
|
|
res = update_reparse_data( ni, xr, value, size );
|
|
}
|
|
xrni = xr->ni;
|
|
ntfs_index_entry_mark_dirty( xr );
|
|
NInoSetDirty( xrni );
|
|
ntfs_index_ctx_put( xr );
|
|
ntfs_inode_close( xrni );
|
|
}
|
|
else
|
|
{
|
|
res = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
errno = EINVAL;
|
|
res = -1;
|
|
}
|
|
return ( res ? -1 : 0 );
|
|
}
|
|
|
|
/*
|
|
* Remove the reparse data
|
|
*
|
|
* Returns 0, or -1 if there is a problem
|
|
*/
|
|
|
|
int ntfs_remove_ntfs_reparse_data( ntfs_inode *ni )
|
|
{
|
|
int res;
|
|
int olderrno;
|
|
ntfs_attr *na;
|
|
ntfs_inode *xrni;
|
|
ntfs_index_context *xr;
|
|
le32 reparse_tag;
|
|
|
|
res = 0;
|
|
if ( ni )
|
|
{
|
|
/*
|
|
* open and delete the reparse data
|
|
*/
|
|
na = ntfs_attr_open( ni, AT_REPARSE_POINT,
|
|
AT_UNNAMED, 0 );
|
|
if ( na )
|
|
{
|
|
/* first remove index (reparse data needed) */
|
|
xr = open_reparse_index( ni->vol );
|
|
if ( xr )
|
|
{
|
|
if ( remove_reparse_index( na, xr,
|
|
&reparse_tag ) < 0 )
|
|
{
|
|
res = -1;
|
|
}
|
|
else
|
|
{
|
|
/* now remove attribute */
|
|
res = ntfs_attr_rm( na );
|
|
if ( !res )
|
|
{
|
|
ni->flags &=
|
|
~FILE_ATTR_REPARSE_POINT;
|
|
NInoFileNameSetDirty( ni );
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If we could not remove the
|
|
* attribute, try to restore the
|
|
* index and log the error. There
|
|
* will be an inconsistency if
|
|
* the reindexing fails.
|
|
*/
|
|
set_reparse_index( ni, xr,
|
|
reparse_tag );
|
|
ntfs_log_error(
|
|
"Failed to remove reparse data."
|
|
" Possible corruption.\n" );
|
|
}
|
|
}
|
|
xrni = xr->ni;
|
|
ntfs_index_entry_mark_dirty( xr );
|
|
NInoSetDirty( xrni );
|
|
ntfs_index_ctx_put( xr );
|
|
ntfs_inode_close( xrni );
|
|
}
|
|
olderrno = errno;
|
|
ntfs_attr_close( na );
|
|
/* avoid errno pollution */
|
|
if ( errno == ENOENT )
|
|
errno = olderrno;
|
|
}
|
|
else
|
|
{
|
|
errno = ENODATA;
|
|
res = -1;
|
|
}
|
|
NInoSetDirty( ni );
|
|
}
|
|
else
|
|
{
|
|
errno = EINVAL;
|
|
res = -1;
|
|
}
|
|
return ( res ? -1 : 0 );
|
|
}
|
|
|
|
#endif /* HAVE_SETXATTR */
|