* Copyright (c) 2005-2007 Yura Pakhuchiy
* Copyright (c) 2005 Yuval Fledel
* Copyright (c) 2006-2009 Szabolcs Szakacsits
* Copyright (c) 2007-2021 Jean-Pierre Andre
* Copyright (c) 2009 Erik Larsson
*
* This program 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 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
*/
#include "lowntfs.h"
#include <stdlib.h>
#include <errno.h>
#include "libntfs/dir.h"
#include "libntfs/reparse.h"
#include "libntfs/misc.h"
#define DISABLE_PLUGINS
#define KERNELPERMS 1
#define INODE(ino) ino
#define ntfs_allowed_dir_access(...) (1) /* permissions checks done elsewhere */
#define set_archive(ni) (ni)->flags |= FILE_ATTR_ARCHIVE
static const char ntfs_bad_reparse[] = "unsupported reparse tag 0x%08lx";
#define ntfs_bad_reparse_lth (sizeof(ntfs_bad_reparse) + 2)
static const char ghostformat[] = ".ghost-ntfs-3g-%020llu";
#define GHOSTLTH 40 /* max length of a ghost file name - see ghostformat */
static u32 ntfs_sequence = 0;
static void
set_fuse_error(int *err)
{
if (!*err)
*err = errno;
}
static void
ntfs_fuse_update_times(ntfs_inode *ni, ntfs_time_update_flags mask)
{
#ifdef __HAIKU__
mask &= ~NTFS_UPDATE_ATIME;
#else
if (ctx->atime == ATIME_DISABLED)
mask &= ~NTFS_UPDATE_ATIME;
else if (ctx->atime == ATIME_RELATIVE && mask == NTFS_UPDATE_ATIME &&
(sle64_to_cpu(ni->last_access_time)
>= sle64_to_cpu(ni->last_data_change_time)) &&
(sle64_to_cpu(ni->last_access_time)
>= sle64_to_cpu(ni->last_mft_change_time)))
return;
#endif
ntfs_inode_update_times(ni, mask);
}
static BOOL ntfs_fuse_fill_security_context(struct lowntfs_context *ctx,
struct SECURITY_CONTEXT *scx)
{
const struct fuse_ctx *fusecontext;
scx->vol = ctx->vol;
scx->mapping[MAPUSERS] = NULL;
scx->mapping[MAPGROUPS] = NULL;
scx->pseccache = NULL;
scx->uid = 0;
scx->gid = 0;
scx->tid = 0;
scx->umask = 0;
return (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL);
}
u64
ntfs_fuse_inode_lookup(struct lowntfs_context *ctx, u64 parent, const char *name)
{
u64 ino = (u64)-1;
u64 inum;
ntfs_inode *dir_ni;
dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
if (dir_ni) {
inum = ntfs_inode_lookup_by_mbsname(dir_ni, name);
if (MREF(inum) <= 1) {
inum = (u64)-1;
errno = ENOENT;
}
if (ntfs_inode_close(dir_ni)
|| (inum == (u64)-1))
ino = (u64)-1;
else
ino = MREF(inum);
}
return (ino);
}
int
ntfs_fuse_getstat(struct lowntfs_context *ctx, struct SECURITY_CONTEXT *scx,
ntfs_inode *ni, struct stat *stbuf)
{
int res = 0;
ntfs_attr *na;
BOOL withusermapping;
memset(stbuf, 0, sizeof(struct stat));
withusermapping = scx && (scx->mapping[MAPUSERS] != (struct MAPPING*)NULL);
stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count);
if (ctx->posix_nlink
&& !(ni->flags & FILE_ATTR_REPARSE_POINT))
stbuf->st_nlink = ntfs_dir_link_cnt(ni);
if ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
|| (ni->flags & FILE_ATTR_REPARSE_POINT)) {
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
res = CALL_REPARSE_PLUGIN(ni, getattr, stbuf);
if (!res) {
apply_umask(stbuf);
} else {
stbuf->st_size = ntfs_bad_reparse_lth;
stbuf->st_blocks =
(ni->allocated_size + 511) >> 9;
stbuf->st_mode = S_IFLNK;
res = 0;
}
goto ok;
#else
char *target;
errno = 0;
target = ntfs_make_symlink(ni, ctx->abs_mnt_point);
* If the reparse point is not a valid
* directory junction, and there is no error
* we still display as a symlink
*/
if (target || (errno == EOPNOTSUPP)) {
if (target)
stbuf->st_size = strlen(target);
else
stbuf->st_size = ntfs_bad_reparse_lth;
stbuf->st_blocks =
(ni->allocated_size + 511) >> 9;
stbuf->st_nlink =
le16_to_cpu(ni->mrec->link_count);
stbuf->st_mode = S_IFLNK;
free(target);
} else {
res = -errno;
goto exit;
}
#endif
} else {
stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask);
if (!test_nino_flag(ni, KnownSize)) {
na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION,
NTFS_INDEX_I30, 4);
if (na) {
ni->data_size = na->data_size;
ni->allocated_size = na->allocated_size;
set_nino_flag(ni, KnownSize);
ntfs_attr_close(na);
}
}
stbuf->st_size = ni->data_size;
stbuf->st_blocks = ni->allocated_size >> 9;
if (!ctx->posix_nlink)
stbuf->st_nlink = 1;
}
} else {
stbuf->st_mode = S_IFREG;
stbuf->st_size = ni->data_size;
#ifdef HAVE_SETXATTR
* return data size rounded to next 512 byte boundary for
* encrypted files to include padding required for decryption
* also include 2 bytes for padding info
*/
if (ctx->efs_raw
&& (ni->flags & FILE_ATTR_ENCRYPTED)
&& ni->data_size)
stbuf->st_size = ((ni->data_size + 511) & ~511) + 2;
#endif
* Temporary fix to make ActiveSync work via Samba 3.0.
* See more on the ntfs-3g-devel list.
*/
stbuf->st_blocks = (ni->allocated_size + 511) >> 9;
if (ni->flags & FILE_ATTR_SYSTEM) {
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
stbuf->st_ino = ni->mft_no;
goto nodata;
}
if (!(ni->flags & FILE_ATTR_HIDDEN)) {
if (na->data_size == 0)
stbuf->st_mode = S_IFIFO;
if (na->data_size == 1)
stbuf->st_mode = S_IFSOCK;
}
* Check whether it's Interix symbolic link, block or
* character device.
*/
if ((u64)na->data_size <= sizeof(INTX_FILE_TYPES)
+ sizeof(ntfschar) * PATH_MAX
&& (u64)na->data_size >
sizeof(INTX_FILE_TYPES)) {
INTX_FILE *intx_file;
intx_file =
(INTX_FILE*)ntfs_malloc(na->data_size);
if (!intx_file) {
res = -errno;
ntfs_attr_close(na);
goto exit;
}
if (ntfs_attr_pread(na, 0, na->data_size,
intx_file) != na->data_size) {
res = -errno;
free(intx_file);
ntfs_attr_close(na);
goto exit;
}
if (intx_file->magic == INTX_BLOCK_DEVICE &&
na->data_size == (s64)offsetof(
INTX_FILE, device_end)) {
stbuf->st_mode = S_IFBLK;
stbuf->st_rdev = makedev(le64_to_cpu(
intx_file->major),
le64_to_cpu(
intx_file->minor));
}
if (intx_file->magic == INTX_CHARACTER_DEVICE &&
na->data_size == (s64)offsetof(
INTX_FILE, device_end)) {
stbuf->st_mode = S_IFCHR;
stbuf->st_rdev = makedev(le64_to_cpu(
intx_file->major),
le64_to_cpu(
intx_file->minor));
}
if (intx_file->magic == INTX_SYMBOLIC_LINK) {
char *target = NULL;
int len;
* symlink target as multibyte string */
len = ntfs_ucstombs(
intx_file->target,
(na->data_size -
offsetof(INTX_FILE,
target)) /
sizeof(ntfschar),
&target, 0);
if (len < 0) {
res = -errno;
free(intx_file);
ntfs_attr_close(na);
goto exit;
}
free(target);
stbuf->st_mode = S_IFLNK;
stbuf->st_size = len;
}
free(intx_file);
}
ntfs_attr_close(na);
}
stbuf->st_mode |= (0777 & ~ctx->fmask);
}
#ifndef DISABLE_PLUGINS
ok:
#endif
if (withusermapping) {
ntfs_get_owner_mode(scx,ni,stbuf);
} else {
#ifndef __HAIKU__
stbuf->st_uid = ctx->uid;
stbuf->st_gid = ctx->gid;
#endif
}
if (S_ISLNK(stbuf->st_mode))
stbuf->st_mode |= 0777;
nodata :
stbuf->st_ino = ni->mft_no;
#ifdef HAVE_STRUCT_STAT_ST_ATIMESPEC
stbuf->st_atimespec = ntfs2timespec(ni->last_access_time);
stbuf->st_ctimespec = ntfs2timespec(ni->last_mft_change_time);
stbuf->st_mtimespec = ntfs2timespec(ni->last_data_change_time);
#elif defined(HAVE_STRUCT_STAT_ST_ATIM)
stbuf->st_atim = ntfs2timespec(ni->last_access_time);
stbuf->st_ctim = ntfs2timespec(ni->last_mft_change_time);
stbuf->st_mtim = ntfs2timespec(ni->last_data_change_time);
#elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC)
{
struct timespec ts;
ts = ntfs2timespec(ni->last_access_time);
stbuf->st_atime = ts.tv_sec;
stbuf->st_atimensec = ts.tv_nsec;
ts = ntfs2timespec(ni->last_mft_change_time);
stbuf->st_ctime = ts.tv_sec;
stbuf->st_ctimensec = ts.tv_nsec;
ts = ntfs2timespec(ni->last_data_change_time);
stbuf->st_mtime = ts.tv_sec;
stbuf->st_mtimensec = ts.tv_nsec;
}
#else
#warning "No known way to set nanoseconds in struct stat !"
{
struct timespec ts;
ts = ntfs2timespec(ni->last_access_time);
stbuf->st_atime = ts.tv_sec;
ts = ntfs2timespec(ni->last_mft_change_time);
stbuf->st_ctime = ts.tv_sec;
ts = ntfs2timespec(ni->last_data_change_time);
stbuf->st_mtime = ts.tv_sec;
}
#endif
#ifdef __HAIKU__
stbuf->st_crtim = ntfs2timespec(ni->creation_time);
#endif
exit:
return (res);
}
int
ntfs_fuse_readlink(struct lowntfs_context* ctx, u64 ino, void* buffer, size_t* bufferSize)
{
ntfs_inode *ni = NULL;
ntfs_attr *na = NULL;
INTX_FILE *intx_file = NULL;
char *buf = (char*)NULL;
int res = 0;
ni = ntfs_inode_open(ctx->vol, INODE(ino));
if (!ni) {
res = -errno;
goto exit;
}
* Reparse point : analyze as a junction point
*/
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
REPARSE_POINT *reparse;
le32 tag;
int lth;
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
res = CALL_REPARSE_PLUGIN(ni, readlink, &buf);
if (res && ((errno == ELIBACC) || (errno == EINVAL)))
errno = EOPNOTSUPP;
#else
errno = 0;
res = 0;
buf = ntfs_make_symlink(ni, ctx->abs_mnt_point);
#endif
if (!buf && (errno == EOPNOTSUPP)) {
buf = (char*)malloc(ntfs_bad_reparse_lth + 1);
if (buf) {
reparse = ntfs_get_reparse_point(ni);
if (reparse) {
tag = reparse->reparse_tag;
free(reparse);
} else
tag = const_cpu_to_le32(0);
lth = snprintf(buf, ntfs_bad_reparse_lth + 1,
ntfs_bad_reparse,
(long)le32_to_cpu(tag));
res = 0;
if (lth != ntfs_bad_reparse_lth) {
free(buf);
buf = (char*)NULL;
}
}
}
if (!buf)
res = -errno;
goto exit;
}
if (!(ni->flags & FILE_ATTR_SYSTEM)) {
res = -EINVAL;
goto exit;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
res = -errno;
goto exit;
}
if ((size_t)na->data_size <= sizeof(INTX_FILE_TYPES)) {
res = -EINVAL;
goto exit;
}
if ((size_t)na->data_size > sizeof(INTX_FILE_TYPES) +
sizeof(ntfschar) * PATH_MAX) {
res = -ENAMETOOLONG;
goto exit;
}
intx_file = (INTX_FILE*)ntfs_malloc(na->data_size);
if (!intx_file) {
res = -errno;
goto exit;
}
if (ntfs_attr_pread(na, 0, na->data_size, intx_file) != na->data_size) {
res = -errno;
goto exit;
}
if (intx_file->magic != INTX_SYMBOLIC_LINK) {
res = -EINVAL;
goto exit;
}
if (ntfs_ucstombs(intx_file->target, (na->data_size -
offsetof(INTX_FILE, target)) / sizeof(ntfschar),
&buf, 0) < 0) {
res = -errno;
goto exit;
}
exit:
if (intx_file)
free(intx_file);
if (na)
ntfs_attr_close(na);
ntfs_inode_close(ni);
#ifdef __HAIKU__
if (buf && !res) {
strlcpy(buffer, buf, *bufferSize);
*bufferSize = strlen(buf);
}
#endif
free(buf);
return res;
}
int
ntfs_fuse_read(ntfs_inode* ni, off_t offset, char* buf, size_t size)
{
ntfs_attr *na = NULL;
int res;
s64 total = 0;
s64 max_read;
if (!size) {
res = 0;
goto exit;
}
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
struct open_file *of;
of = (struct open_file*)(long)fi->fh;
res = CALL_REPARSE_PLUGIN(ni, read, buf, size, offset, &of->fi);
if (res >= 0) {
goto stamps;
}
#else
errno = EOPNOTSUPP;
res = errno;
#endif
goto exit;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
res = errno;
goto exit;
}
max_read = na->data_size;
#ifdef HAVE_SETXATTR
if (ctx->efs_raw
&& max_read
&& (na->data_flags & ATTR_IS_ENCRYPTED)
&& NAttrNonResident(na)) {
max_read = ((na->data_size+511) & ~511) + 2;
}
#endif
if (offset + (off_t)size > max_read) {
if (max_read < offset)
goto ok;
size = max_read - offset;
}
while (size > 0) {
s64 ret = ntfs_attr_pread(na, offset, size, buf + total);
if (ret != (s64)size)
ntfs_log_perror("ntfs_attr_pread error reading inode %lld at "
"offset %lld: %lld <> %lld", (long long)ni->mft_no,
(long long)offset, (long long)size, (long long)ret);
if (ret <= 0 || ret > (s64)size) {
res = (ret < 0) ? errno : EIO;
goto exit;
}
size -= ret;
offset += ret;
total += ret;
}
ok:
res = total;
#ifndef DISABLE_PLUGINS
stamps :
#endif
ntfs_fuse_update_times(ni, NTFS_UPDATE_ATIME);
exit:
if (na)
ntfs_attr_close(na);
return res;
}
int
ntfs_fuse_write(struct lowntfs_context* ctx, ntfs_inode* ni, const char *buf,
size_t size, off_t offset)
{
ntfs_attr *na = NULL;
int res, total = 0;
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
struct open_file *of;
of = (struct open_file*)(long)fi->fh;
res = CALL_REPARSE_PLUGIN(ni, write, buf, size, offset,
&of->fi);
if (res >= 0) {
goto stamps;
}
#else
res = EOPNOTSUPP;
errno = EOPNOTSUPP;
#endif
goto exit;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
res = errno;
goto exit;
}
while (size) {
s64 ret = ntfs_attr_pwrite(na, offset, size, buf + total);
if (ret <= 0) {
res = errno;
goto exit;
}
size -= ret;
offset += ret;
total += ret;
}
res = total;
#ifndef DISABLE_PLUGINS
stamps :
#endif
if ((res > 0)
&& (!ctx->dmtime
|| (sle64_to_cpu(ntfs_current_time())
- sle64_to_cpu(ni->last_data_change_time)) > ctx->dmtime))
ntfs_fuse_update_times(ni, NTFS_UPDATE_MCTIME);
exit:
if (na)
ntfs_attr_close(na);
if (res > 0)
set_archive(ni);
return res;
}
int
ntfs_fuse_create(struct lowntfs_context *ctx, ino_t parent, const char *name,
mode_t typemode, dev_t dev, const char *target, ino_t* ino)
{
ntfschar *uname = NULL, *utarget = NULL;
ntfs_inode *dir_ni = NULL, *ni;
struct open_file *of;
int state = 0;
le32 securid;
gid_t gid;
mode_t dsetgid;
mode_t type = typemode & ~07777;
mode_t perm;
struct SECURITY_CONTEXT security;
int res = 0, uname_len, utarget_len;
const int fi = 1;
uname_len = ntfs_mbstoucs(name, &uname);
if ((uname_len < 0)
|| (ctx->windows_names
&& ntfs_forbidden_names(ctx->vol,uname,uname_len,TRUE))) {
res = -errno;
goto exit;
}
if (parent == FILE_Extend) {
errno = EPERM;
res = -errno;
goto exit;
}
dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
if (!dir_ni) {
res = -errno;
goto exit;
}
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
if (!ntfs_fuse_fill_security_context(ctx, &security)
|| ntfs_allowed_create(&security,
dir_ni, &gid, &dsetgid)) {
#else
ntfs_fuse_fill_security_context(ctx, &security);
ntfs_allowed_create(&security, dir_ni, &gid, &dsetgid);
#endif
if (S_ISDIR(type))
perm = (typemode & ~ctx->dmask & 0777)
| (dsetgid & S_ISGID);
else
if ((ctx->special_files == NTFS_FILES_WSL)
&& S_ISLNK(type))
perm = typemode | 0777;
else
perm = typemode & ~ctx->fmask & 0777;
* Try to get a security id available for
* file creation (from inheritance or argument).
* This is not possible for NTFS 1.x, and we will
* have to build a security attribute later.
*/
if (!security.mapping[MAPUSERS])
securid = const_cpu_to_le32(0);
else
if (ctx->inherit)
securid = ntfs_inherited_id(&security,
dir_ni, S_ISDIR(type));
else
#if POSIXACLS
securid = ntfs_alloc_securid(&security,
security.uid, gid,
dir_ni, perm, S_ISDIR(type));
#else
securid = ntfs_alloc_securid(&security,
security.uid, gid,
perm & ~security.umask, S_ISDIR(type));
#endif
if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
reparse = (REPARSE_POINT*)NULL;
ops = select_reparse_plugin(ctx, dir_ni, &reparse);
if (ops && ops->create) {
ni = (*ops->create)(dir_ni, reparse,
securid, uname, uname_len, type);
} else {
ni = (ntfs_inode*)NULL;
errno = EOPNOTSUPP;
}
free(reparse);
#else
ni = (ntfs_inode*)NULL;
errno = EOPNOTSUPP;
res = -errno;
#endif
} else {
switch (type) {
case S_IFCHR:
case S_IFBLK:
ni = ntfs_create_device(dir_ni, securid,
uname, uname_len,
type, dev);
break;
case S_IFLNK:
utarget_len = ntfs_mbstoucs(target,
&utarget);
if (utarget_len < 0) {
res = -errno;
goto exit;
}
ni = ntfs_create_symlink(dir_ni,
securid,
uname, uname_len,
utarget, utarget_len);
break;
default:
ni = ntfs_create(dir_ni, securid, uname,
uname_len, type);
break;
}
}
if (ni) {
* set the security attribute if a security id
* could not be allocated (eg NTFS 1.x)
*/
if (security.mapping[MAPUSERS]) {
#if POSIXACLS
if (!securid
&& ntfs_set_inherited_posix(&security, ni,
security.uid, gid,
dir_ni, perm) < 0)
set_fuse_error(&res);
#else
if (!securid
&& ntfs_set_owner_mode(&security, ni,
security.uid, gid,
perm & ~security.umask) < 0)
set_fuse_error(&res);
#endif
}
set_archive(ni);
if (fi && (ni->flags & FILE_ATTR_COMPRESSED)) {
state |= CLOSE_COMPRESSED;
}
#ifdef HAVE_SETXATTR
if (fi
&& ctx->efs_raw
&& (ni->flags & FILE_ATTR_ENCRYPTED))
state |= CLOSE_ENCRYPTED;
#endif
if (fi && ctx->dmtime)
state |= CLOSE_DMTIME;
ntfs_inode_update_mbsname(dir_ni, name, ni->mft_no);
NInoSetDirty(ni);
*ino = ni->mft_no;
#ifndef __HAIKU__
e->ino = ni->mft_no;
e->generation = 1;
e->attr_timeout = ATTR_TIMEOUT;
e->entry_timeout = ENTRY_TIMEOUT;
res = ntfs_fuse_getstat(&security, ni, &e->attr);
#endif
* closing ni requires access to dir_ni to
* synchronize the index, avoid double opening.
*/
if (ntfs_inode_close_in_dir(ni, dir_ni))
set_fuse_error(&res);
ntfs_fuse_update_times(dir_ni, NTFS_UPDATE_MCTIME);
} else
res = -errno;
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
} else
res = -errno;
#endif
exit:
free(uname);
if (ntfs_inode_close(dir_ni))
set_fuse_error(&res);
if (utarget)
free(utarget);
#ifndef __HAIKU__
if ((res >= 0) && fi) {
of = (struct open_file*)malloc(sizeof(struct open_file));
if (of) {
of->parent = 0;
of->ino = e->ino;
of->state = state;
of->next = ctx->open_files;
of->previous = (struct open_file*)NULL;
if (ctx->open_files)
ctx->open_files->previous = of;
ctx->open_files = of;
fi->fh = (long)of;
}
}
#else
#endif
return res;
}
static int ntfs_fuse_newlink(struct lowntfs_context *ctx,
ino_t ino, ino_t newparent,
const char *newname, struct fuse_entry_param *e)
{
ntfschar *uname = NULL;
ntfs_inode *dir_ni = NULL, *ni;
int res = 0, uname_len;
struct SECURITY_CONTEXT security;
ni = ntfs_inode_open(ctx->vol, INODE(ino));
if (!ni) {
res = -errno;
goto exit;
}
if (e && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) {
errno = EPERM;
res = -errno;
goto exit;
}
uname_len = ntfs_mbstoucs(newname, &uname);
if ((uname_len < 0)
|| (ctx->windows_names
&& ntfs_forbidden_names(ctx->vol,uname,uname_len,TRUE))) {
res = -errno;
goto exit;
}
dir_ni = ntfs_inode_open(ctx->vol, INODE(newparent));
if (!dir_ni) {
res = -errno;
goto exit;
}
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
if (ntfs_fuse_fill_security_context(ctx, &security)
&& !ntfs_allowed_access(&security,dir_ni,S_IWRITE + S_IEXEC))
res = -EACCES;
else
#else
ntfs_fuse_fill_security_context(ctx, &security);
#endif
{
if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
res = CALL_REPARSE_PLUGIN(dir_ni, link,
ni, uname, uname_len);
if (res < 0)
goto exit;
#else
res = -EOPNOTSUPP;
goto exit;
#endif
} else {
if (ntfs_link(ni, dir_ni, uname, uname_len)) {
res = -errno;
goto exit;
}
}
ntfs_inode_update_mbsname(dir_ni, newname, ni->mft_no);
#ifndef __HAIKU__
if (e) {
e->ino = ni->mft_no;
e->generation = 1;
e->attr_timeout = ATTR_TIMEOUT;
e->entry_timeout = ENTRY_TIMEOUT;
res = ntfs_fuse_getstat(&security, ni, &e->attr);
}
#endif
set_archive(ni);
ntfs_fuse_update_times(ni, NTFS_UPDATE_CTIME);
ntfs_fuse_update_times(dir_ni, NTFS_UPDATE_MCTIME);
}
exit:
* Must close dir_ni first otherwise ntfs_inode_sync_file_name(ni)
* may fail because ni may not be in parent's index on the disk yet.
*/
if (ntfs_inode_close(dir_ni))
set_fuse_error(&res);
if (ntfs_inode_close(ni))
set_fuse_error(&res);
free(uname);
return (res);
}
int
ntfs_fuse_rm(struct lowntfs_context *ctx, ino_t parent, const char *name,
enum RM_TYPES rm_type)
{
ntfschar *uname = NULL;
ntfschar *ugname;
ntfs_inode *dir_ni = NULL, *ni = NULL;
int res = 0, uname_len;
int ugname_len;
u64 iref;
ino_t ino;
char ghostname[GHOSTLTH];
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
struct SECURITY_CONTEXT security;
#endif
#if defined(__sun) && defined (__SVR4)
int isdir;
#endif
int no_check_open = (rm_type & RM_NO_CHECK_OPEN) != 0;
rm_type &= ~RM_NO_CHECK_OPEN;
if (parent == FILE_Extend) {
res = -EPERM;
goto exit;
}
dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
if (!dir_ni) {
res = -errno;
goto exit;
}
uname_len = ntfs_mbstoucs(name, &uname);
if (uname_len < 0) {
res = -errno;
goto exit;
}
iref = ntfs_inode_lookup_by_mbsname(dir_ni, name);
if (iref == (u64)-1) {
res = -errno;
goto exit;
}
ino = (ino_t)MREF(iref);
if (ino < FILE_first_user) {
errno = EPERM;
res = -errno;
goto exit;
}
ni = ntfs_inode_open(ctx->vol, ino);
if (!ni) {
res = -errno;
goto exit;
}
#if defined(__sun) && defined (__SVR4)
isdir = ni->mrec->flags & MFT_RECORD_IS_DIRECTORY;
#ifndef DISABLE_PLUGINS
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
struct stat st;
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
res = ntfs_inode_close(dir_ni);
if (res)
goto exit;
dir_ni = (ntfs_inode*)NULL;
res = CALL_REPARSE_PLUGIN(ni, getattr, &st);
if (res)
goto exit;
isdir = S_ISDIR(st.st_mode);
dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
if (!dir_ni) {
res = -errno;
goto exit;
}
}
#endif
if (rm_type == (isdir ? RM_LINK : RM_DIR)) {
errno = (isdir ? EISDIR : ENOTDIR);
res = -errno;
goto exit;
}
#endif
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
if (ntfs_fuse_fill_security_context(ctx, &security)
&& !ntfs_allowed_dir_access(&security, dir_ni, ino, ni,
S_IEXEC + S_IWRITE + S_ISVTX)) {
errno = EACCES;
res = -errno;
goto exit;
}
#endif
#ifndef __HAIKU__
* We keep one open_file record per opening, to avoid
* having to check the list of open files when opening
* and closing (which are more frequent than unlinking).
* As a consequence, we may have to create several
* ghosts names for the same file.
* The file may have been opened with a different name
* in a different parent directory. The ghost is
* nevertheless created in the parent directory of the
* name being unlinked, and permissions to do so are the
* same as required for unlinking.
*/
for (of=ctx->open_files; of; of = of->next) {
if ((of->ino == ino) && !(of->state & CLOSE_GHOST)) {
#else
int* close_state = NULL;
if (!no_check_open) {
close_state = ntfs_haiku_get_close_state(ctx, ino);
if (close_state && (*close_state & CLOSE_GHOST) == 0) {
#endif
/* file was open, create a ghost in unlink parent */
ntfs_inode *gni;
u64 gref;
if (ni) {
if (ntfs_inode_close(ni)) {
res = -errno;
goto exit;
}
ni = (ntfs_inode*)NULL;
}
*close_state |= CLOSE_GHOST;
u64 ghost = ++ctx->latest_ghost;
sprintf(ghostname,ghostformat,ghost);
ugname = (ntfschar*)NULL;
ugname_len = ntfs_mbstoucs(ghostname, &ugname);
if (ugname_len < 0) {
res = -errno;
goto exit;
}
gref = ntfs_inode_lookup_by_mbsname(dir_ni, ghostname);
if (gref != (u64)-1) {
gni = ntfs_inode_open(ctx->vol, MREF(gref));
ntfs_delete(ctx->vol, (char*)NULL, gni, dir_ni,
ugname, ugname_len);
dir_ni = (ntfs_inode*)NULL;
} else {
if (ntfs_inode_close(dir_ni)) {
res = -errno;
goto out;
}
dir_ni = (ntfs_inode*)NULL;
}
free(ugname);
res = ntfs_fuse_newlink(ctx, ino, parent, ghostname,
(struct fuse_entry_param*)NULL);
if (res)
goto out;
#ifdef __HAIKU__
ntfs_haiku_put_close_state(ctx, ino, ghost);
close_state = NULL;
#endif
dir_ni = ntfs_inode_open(ctx->vol, INODE(parent));
if (!dir_ni) {
res = -errno;
goto exit;
}
}
}
if (!ni) {
ni = ntfs_inode_open(ctx->vol, ino);
if (!ni) {
res = -errno;
goto exit;
}
}
if (dir_ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
res = CALL_REPARSE_PLUGIN(dir_ni, unlink, (char*)NULL,
ni, uname, uname_len);
#else
errno = EOPNOTSUPP;
res = -errno;
#endif
} else {
if (ntfs_delete(ctx->vol, (char*)NULL, ni, dir_ni,
uname, uname_len))
res = -errno;
}
ni = dir_ni = NULL;
exit:
if (ntfs_inode_close(ni) && !res)
res = -errno;
if (ntfs_inode_close(dir_ni) && !res)
res = -errno;
out :
#ifdef __HAIKU__
if (close_state)
ntfs_haiku_put_close_state(ctx, ino, -1);
#endif
free(uname);
return res;
}
int ntfs_fuse_release(struct lowntfs_context *ctx, ino_t parent, ino_t ino, int state, u64 ghost)
{
ntfs_inode *ni = NULL;
ntfs_attr *na = NULL;
char ghostname[GHOSTLTH];
int res;
if (!(state & (CLOSE_COMPRESSED | CLOSE_ENCRYPTED
| CLOSE_DMTIME | CLOSE_REPARSE))) {
res = 0;
goto out;
}
ni = ntfs_inode_open(ctx->vol, INODE(ino));
if (!ni) {
res = -errno;
goto exit;
}
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
#ifndef DISABLE_PLUGINS
const plugin_operations_t *ops;
REPARSE_POINT *reparse;
res = CALL_REPARSE_PLUGIN(ni, release, &of->fi);
if (!res) {
goto stamps;
}
#else
res = 0;
#endif
goto exit;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
res = -errno;
goto exit;
}
res = 0;
if (state & CLOSE_COMPRESSED)
res = ntfs_attr_pclose(na);
#ifdef HAVE_SETXATTR
if (of->state & CLOSE_ENCRYPTED)
res = ntfs_efs_fixup_attribute(NULL, na);
#endif
#ifndef DISABLE_PLUGINS
stamps :
#endif
if (state & CLOSE_DMTIME)
ntfs_inode_update_times(ni,NTFS_UPDATE_MCTIME);
exit:
if (na)
ntfs_attr_close(na);
if (ntfs_inode_close(ni))
set_fuse_error(&res);
out:
if (1) {
if (state & CLOSE_GHOST) {
sprintf(ghostname,ghostformat,ghost);
ntfs_fuse_rm(ctx, parent, ghostname, RM_ANY | RM_NO_CHECK_OPEN);
}
#ifndef __HAIKU__
if (of->next)
of->next->previous = of->previous;
if (of->previous)
of->previous->next = of->next;
else
ctx->open_files = of->next;
free(of);
#endif
}
#ifndef __HAIKU__
if (res)
fuse_reply_err(req, -res);
else
fuse_reply_err(req, 0);
#endif
return res;
}
static int ntfs_fuse_safe_rename(struct lowntfs_context *ctx, ino_t ino,
ino_t parent, const char *name, ino_t xino,
ino_t newparent, const char *newname,
const char *tmp)
{
int ret;
ntfs_log_trace("Entering\n");
ret = ntfs_fuse_newlink(ctx, xino, newparent, tmp,
(struct fuse_entry_param*)NULL);
if (ret)
return ret;
ret = ntfs_fuse_rm(ctx, newparent, newname, RM_ANY);
if (!ret) {
ret = ntfs_fuse_newlink(ctx, ino, newparent, newname,
(struct fuse_entry_param*)NULL);
if (ret)
goto restore;
ret = ntfs_fuse_rm(ctx, parent, name, RM_ANY | RM_NO_CHECK_OPEN);
if (ret) {
if (ntfs_fuse_rm(ctx, newparent, newname, RM_ANY))
goto err;
goto restore;
}
}
goto cleanup;
restore:
if (ntfs_fuse_newlink(ctx, xino, newparent, newname,
(struct fuse_entry_param*)NULL)) {
err:
ntfs_log_perror("Rename failed. Existing file '%s' was renamed "
"to '%s'", newname, tmp);
} else {
cleanup:
* Condition for this unlink has already been checked in
* "ntfs_fuse_rename_existing_dest()", so it should never
* fail (unless concurrent access to directories when fuse
* is multithreaded)
*/
if (ntfs_fuse_rm(ctx, newparent, tmp, RM_ANY) < 0)
ntfs_log_perror("Rename failed. Existing file '%s' still present "
"as '%s'", newname, tmp);
}
return ret;
}
static int ntfs_fuse_rename_existing_dest(struct lowntfs_context *ctx, ino_t ino,
ino_t parent, const char *name,
ino_t xino, ino_t newparent,
const char *newname)
{
int ret, len;
char *tmp;
const char *ext = ".ntfs-3g-";
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
ntfs_inode *newdir_ni;
struct SECURITY_CONTEXT security;
#endif
ntfs_log_trace("Entering\n");
len = strlen(newname) + strlen(ext) + 10 + 1;
tmp = (char*)ntfs_malloc(len);
if (!tmp)
return -errno;
ret = snprintf(tmp, len, "%s%s%010d", newname, ext, ++ntfs_sequence);
if (ret != len - 1) {
ntfs_log_error("snprintf failed: %d != %d\n", ret, len - 1);
ret = -EOVERFLOW;
} else {
#if !KERNELPERMS | (POSIXACLS & !KERNELACLS)
* Make sure existing dest can be removed.
* This is only needed if parent directory is
* sticky, because in this situation condition
* for unlinking is different from condition for
* linking
*/
newdir_ni = ntfs_inode_open(ctx->vol, INODE(newparent));
if (newdir_ni) {
if (!ntfs_fuse_fill_security_context(ctx,&security)
|| ntfs_allowed_dir_access(&security, newdir_ni,
xino, (ntfs_inode*)NULL,
S_IEXEC + S_IWRITE + S_ISVTX)) {
if (ntfs_inode_close(newdir_ni))
ret = -errno;
else
ret = ntfs_fuse_safe_rename(ctx, ino,
parent, name, xino,
newparent, newname,
tmp);
} else {
ntfs_inode_close(newdir_ni);
ret = -EACCES;
}
} else
ret = -errno;
#else
ret = ntfs_fuse_safe_rename(ctx, ino, parent, name,
xino, newparent, newname, tmp);
#endif
}
free(tmp);
return ret;
}
int ntfs_fuse_rename(struct lowntfs_context *ctx, ino_t parent,
const char *name, ino_t newparent,
const char *newname)
{
int ret;
ino_t ino;
ino_t xino;
ntfs_inode *ni;
ntfs_log_debug("rename: old: '%s' new: '%s'\n", name, newname);
* FIXME: Rename should be atomic.
*/
ino = ntfs_fuse_inode_lookup(ctx, parent, name);
if (ino == (ino_t)-1) {
ret = -errno;
goto out;
}
xino = ntfs_fuse_inode_lookup(ctx, newparent, newname);
if (xino != (ino_t)-1) {
* Target exists : no need to check whether it
* designates the same inode, this has already
* been checked (by fuse ?)
*/
ni = ntfs_inode_open(ctx->vol, INODE(xino));
if (!ni)
ret = -errno;
else {
ret = ntfs_check_empty_dir(ni);
if (ret < 0) {
ret = -errno;
ntfs_inode_close(ni);
goto out;
}
if (ntfs_inode_close(ni)) {
set_fuse_error(&ret);
goto out;
}
ret = ntfs_fuse_rename_existing_dest(ctx, ino, parent,
name, xino, newparent, newname);
}
} else {
ret = ntfs_fuse_newlink(ctx, ino, newparent, newname,
(struct fuse_entry_param*)NULL);
if (ret)
goto out;
ret = ntfs_fuse_rm(ctx, parent, name, RM_ANY | RM_NO_CHECK_OPEN);
if (ret)
ntfs_fuse_rm(ctx, newparent, newname, RM_ANY);
}
out:
#ifndef __HAIKU__
if (ret)
fuse_reply_err(req, -ret);
else
fuse_reply_err(req, 0);
#endif
return ret;
}