#include <sys/synch.h>
#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <sys/nbmlock.h>
#define SMB_RENAME_FLAG_OVERWRITE 0x001
static int smb_rename_check_stream(smb_fqi_t *, smb_fqi_t *);
static int smb_rename_check_attr(smb_request_t *, smb_node_t *, uint16_t);
static int smb_rename_lookup_src(smb_request_t *);
static uint32_t smb_rename_check_src(smb_request_t *, smb_fqi_t *);
static void smb_rename_release_src(smb_request_t *);
static uint32_t smb_rename_errno2status(int);
uint32_t
smb_setinfo_rename(smb_request_t *sr, smb_node_t *node, char *path, int flags)
{
smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi;
smb_pathname_t *dst_pn = &dst_fqi->fq_path;
uint32_t status;
sr->arg.dirop.flags = flags ? SMB_RENAME_FLAG_OVERWRITE : 0;
sr->arg.dirop.info_level = FileRenameInformation;
src_fqi->fq_sattr = SMB_SEARCH_ATTRIBUTES;
src_fqi->fq_fnode = node;
src_fqi->fq_dnode = node->n_dnode;
smb_pathname_init(sr, dst_pn, path);
if (!smb_pathname_validate(sr, dst_pn))
return (NT_STATUS_OBJECT_NAME_INVALID);
status = smb_common_rename(sr, src_fqi, dst_fqi);
return (status);
}
uint32_t
smb_common_rename(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
{
smb_node_t *src_fnode, *src_dnode, *dst_dnode;
smb_node_t *dst_fnode = 0;
smb_node_t *tnode;
char *new_name, *path;
DWORD status;
int rc;
tnode = sr->tid_tree->t_snode;
path = dst_fqi->fq_path.pn_path;
rc = smb_rename_check_stream(src_fqi, dst_fqi);
if (rc != 0)
return (smb_rename_errno2status(rc));
if (src_fqi->fq_fnode) {
smb_node_ref(src_fqi->fq_dnode);
smb_node_ref(src_fqi->fq_fnode);
} else {
rc = smb_rename_lookup_src(sr);
if (rc != 0)
return (smb_rename_errno2status(rc));
}
src_fnode = src_fqi->fq_fnode;
src_dnode = src_fqi->fq_dnode;
status = smb_rename_check_src(sr, src_fqi);
if (status != NT_STATUS_SUCCESS) {
smb_node_release(src_fqi->fq_fnode);
smb_node_release(src_fqi->fq_dnode);
return (status);
}
if (dst_fqi->fq_dnode) {
smb_node_ref(dst_fqi->fq_dnode);
} else {
rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
&dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
if (rc != 0) {
smb_rename_release_src(sr);
return (smb_rename_errno2status(rc));
}
}
dst_dnode = dst_fqi->fq_dnode;
new_name = dst_fqi->fq_last_comp;
if ((src_dnode == dst_dnode) &&
(strcmp(src_fnode->od_name, new_name) == 0)) {
smb_rename_release_src(sr);
smb_node_release(dst_dnode);
return (0);
}
rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
dst_dnode, new_name, &dst_fqi->fq_fnode);
if (rc == ENOENT) {
if (smb_is_invalid_filename(new_name)) {
smb_rename_release_src(sr);
smb_node_release(dst_dnode);
return (NT_STATUS_OBJECT_NAME_INVALID);
}
}
if ((rc == 0) &&
(src_dnode == dst_dnode) &&
(smb_strcasecmp(src_fnode->od_name,
dst_fqi->fq_fnode->od_name, 0) == 0)) {
smb_node_release(dst_fqi->fq_fnode);
dst_fqi->fq_fnode = NULL;
if (smb_tree_has_feature(sr->tid_tree,
SMB_TREE_NO_CASESENSITIVE)) {
if (smb_strcasecmp(src_fnode->od_name,
dst_fqi->fq_last_comp, 0) != 0) {
smb_rename_release_src(sr);
smb_node_release(dst_dnode);
return (0);
}
} else {
rc = smb_fsop_lookup(sr, sr->user_cr,
SMB_CASE_SENSITIVE, tnode, dst_dnode, new_name,
&dst_fqi->fq_fnode);
if ((rc == 0) &&
(dst_fqi->fq_fnode == src_fnode)) {
smb_rename_release_src(sr);
smb_node_release(dst_fqi->fq_fnode);
smb_node_release(dst_dnode);
return (0);
}
}
}
if ((rc != 0) && (rc != ENOENT)) {
smb_rename_release_src(sr);
smb_node_release(dst_fqi->fq_dnode);
return (smb_rename_errno2status(rc));
}
if (dst_fqi->fq_fnode) {
dst_fnode = dst_fqi->fq_fnode;
if ((sr->arg.dirop.flags & SMB_RENAME_FLAG_OVERWRITE) == 0) {
smb_rename_release_src(sr);
smb_node_release(dst_fnode);
smb_node_release(dst_dnode);
return (NT_STATUS_OBJECT_NAME_COLLISION);
}
status = smb_oplock_break_DELETE(dst_fnode, NULL);
if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
if (sr->session->dialect >= SMB_VERS_2_BASE)
(void) smb2sr_go_async(sr);
(void) smb_oplock_wait_break(sr, dst_fnode, 0);
status = 0;
}
if (status != 0) {
smb_rename_release_src(sr);
smb_node_release(dst_fnode);
smb_node_release(dst_dnode);
return (status);
}
smb_node_rdlock(dst_fnode);
status = smb_node_delete_check(dst_fnode);
if (status != NT_STATUS_SUCCESS) {
smb_node_unlock(dst_fnode);
smb_rename_release_src(sr);
smb_node_release(dst_fnode);
smb_node_release(dst_dnode);
return (NT_STATUS_ACCESS_DENIED);
}
nbl_start_crit(dst_fnode->vp, RW_READER);
status = smb_nbl_conflict(dst_fnode, 0, UINT64_MAX, NBL_REMOVE);
if (status != NT_STATUS_SUCCESS) {
smb_node_end_crit(dst_fnode);
smb_rename_release_src(sr);
smb_node_release(dst_fnode);
smb_node_release(dst_dnode);
return (NT_STATUS_ACCESS_DENIED);
}
new_name = dst_fnode->od_name;
}
rc = smb_fsop_rename(sr, sr->user_cr,
src_dnode, src_fnode->od_name,
dst_dnode, new_name);
if (rc == 0) {
int a_src, a_dst;
if (src_dnode == dst_dnode) {
a_src = FILE_ACTION_RENAMED_OLD_NAME;
a_dst = FILE_ACTION_RENAMED_NEW_NAME;
} else {
a_src = FILE_ACTION_REMOVED;
a_dst = FILE_ACTION_ADDED;
}
smb_node_notify_change(src_dnode, a_src, src_fnode->od_name);
smb_node_notify_change(dst_dnode, a_dst, new_name);
}
smb_rename_release_src(sr);
if (dst_fqi->fq_fnode) {
smb_node_end_crit(dst_fnode);
smb_node_release(dst_fnode);
}
smb_node_release(dst_dnode);
return (smb_rename_errno2status(rc));
}
static int
smb_rename_check_stream(smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
{
smb_node_t *src_fnode = src_fqi->fq_fnode;
char *src_path = src_fqi->fq_path.pn_path;
char *dst_path = dst_fqi->fq_path.pn_path;
if ((dst_path[0] == ':') ||
((dst_path[0] == '\\') && (dst_path[1] == ':'))) {
return (EACCES);
}
if (smb_is_stream_name(dst_path))
return (EINVAL);
if (src_fqi->fq_fnode) {
if (SMB_IS_STREAM(src_fnode))
return (EINVAL);
} else {
if (smb_is_stream_name(src_path))
return (EINVAL);
}
return (0);
}
uint32_t
smb_setinfo_link(smb_request_t *sr, smb_node_t *node, char *path, int flags)
{
smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
smb_fqi_t *dst_fqi = &sr->arg.dirop.dst_fqi;
smb_pathname_t *dst_pn = &dst_fqi->fq_path;
uint32_t status;
sr->arg.dirop.flags = flags ? SMB_RENAME_FLAG_OVERWRITE : 0;
sr->arg.dirop.info_level = FileLinkInformation;
src_fqi->fq_sattr = SMB_SEARCH_ATTRIBUTES;
src_fqi->fq_fnode = node;
src_fqi->fq_dnode = node->n_dnode;
smb_pathname_init(sr, dst_pn, path);
if (!smb_pathname_validate(sr, dst_pn))
return (NT_STATUS_OBJECT_NAME_INVALID);
status = smb_make_link(sr, src_fqi, dst_fqi);
return (status);
}
uint32_t
smb_make_link(smb_request_t *sr, smb_fqi_t *src_fqi, smb_fqi_t *dst_fqi)
{
smb_node_t *tnode;
char *path;
int rc;
tnode = sr->tid_tree->t_snode;
path = dst_fqi->fq_path.pn_path;
if (smb_is_stream_name(src_fqi->fq_path.pn_path) ||
smb_is_stream_name(dst_fqi->fq_path.pn_path)) {
return (NT_STATUS_INVALID_PARAMETER);
}
if (src_fqi->fq_fnode) {
smb_node_ref(src_fqi->fq_dnode);
smb_node_ref(src_fqi->fq_fnode);
} else {
rc = smb_rename_lookup_src(sr);
if (rc != 0)
return (smb_rename_errno2status(rc));
}
if (smb_node_is_dir(src_fqi->fq_fnode)) {
smb_node_release(src_fqi->fq_dnode);
smb_node_release(src_fqi->fq_fnode);
return (NT_STATUS_FILE_IS_A_DIRECTORY);
}
smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
if (dst_fqi->fq_dnode) {
smb_node_ref(dst_fqi->fq_dnode);
} else {
rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
&dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
if (rc != 0) {
smb_rename_release_src(sr);
return (smb_rename_errno2status(rc));
}
}
if ((src_fqi->fq_dnode == dst_fqi->fq_dnode) &&
(smb_strcasecmp(src_fqi->fq_fnode->od_name,
dst_fqi->fq_last_comp, 0) == 0)) {
smb_rename_release_src(sr);
smb_node_release(dst_fqi->fq_dnode);
return (0);
}
if (smb_is_invalid_filename(dst_fqi->fq_last_comp)) {
smb_rename_release_src(sr);
smb_node_release(dst_fqi->fq_dnode);
return (NT_STATUS_OBJECT_NAME_INVALID);
}
rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
dst_fqi->fq_dnode, dst_fqi->fq_last_comp, &dst_fqi->fq_fnode);
if (rc == 0) {
smb_node_release(dst_fqi->fq_fnode);
rc = EEXIST;
}
if (rc != ENOENT) {
smb_rename_release_src(sr);
smb_node_release(dst_fqi->fq_dnode);
return (smb_rename_errno2status(rc));
}
rc = smb_fsop_link(sr, sr->user_cr, src_fqi->fq_fnode,
dst_fqi->fq_dnode, dst_fqi->fq_last_comp);
if (rc == 0) {
smb_node_notify_change(dst_fqi->fq_dnode,
FILE_ACTION_ADDED, dst_fqi->fq_last_comp);
}
smb_rename_release_src(sr);
smb_node_release(dst_fqi->fq_dnode);
return (smb_rename_errno2status(rc));
}
static int
smb_rename_lookup_src(smb_request_t *sr)
{
smb_node_t *tnode;
char *path;
int rc;
smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
if (smb_is_stream_name(src_fqi->fq_path.pn_path))
return (EINVAL);
tnode = sr->tid_tree->t_snode;
path = src_fqi->fq_path.pn_path;
rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
&src_fqi->fq_dnode, src_fqi->fq_last_comp);
if (rc != 0)
return (rc);
rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode);
if (rc != 0) {
smb_node_release(src_fqi->fq_dnode);
return (rc);
}
rc = smb_rename_check_attr(sr, src_fqi->fq_fnode, src_fqi->fq_sattr);
if (rc != 0) {
smb_node_release(src_fqi->fq_fnode);
smb_node_release(src_fqi->fq_dnode);
return (rc);
}
return (0);
}
static uint32_t
smb_rename_check_src(smb_request_t *sr, smb_fqi_t *src_fqi)
{
smb_node_t *src_node = src_fqi->fq_fnode;
uint32_t status;
if (sr->fid_ofile != NULL) {
status = smb_oplock_break_SETINFO(src_node, sr->fid_ofile,
FileRenameInformation);
if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
if (sr->session->dialect >= SMB_VERS_2_BASE)
(void) smb2sr_go_async(sr);
(void) smb_oplock_wait_break(sr, src_node, 0);
status = 0;
}
smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
return (NT_STATUS_SUCCESS);
}
ASSERT(sr->session->dialect < SMB_VERS_2_BASE);
status = smb_oplock_break_DELETE(src_node, NULL);
if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
(void) smb_oplock_wait_break(sr, src_node, 0);
}
smb_node_rdlock(src_node);
status = smb_node_rename_check(src_node);
if (status != NT_STATUS_SUCCESS) {
smb_node_unlock(src_node);
return (status);
}
status = smb_oplock_break_SETINFO(src_node, NULL,
FileRenameInformation);
if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
(void) smb_oplock_wait_break(sr, src_node, 0);
}
nbl_start_crit(src_node->vp, RW_READER);
status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME);
if (status != NT_STATUS_SUCCESS) {
smb_node_end_crit(src_node);
}
return (status);
}
static void
smb_rename_release_src(smb_request_t *sr)
{
smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
smb_node_end_crit(src_fqi->fq_fnode);
smb_node_release(src_fqi->fq_fnode);
smb_node_release(src_fqi->fq_dnode);
}
static int
smb_rename_check_attr(smb_request_t *sr, smb_node_t *node, uint16_t sattr)
{
smb_attr_t attr;
bzero(&attr, sizeof (attr));
attr.sa_mask = SMB_AT_DOSATTR;
if (smb_node_getattr(sr, node, zone_kcred(), NULL, &attr) != 0)
return (EACCES);
if ((attr.sa_dosattr & FILE_ATTRIBUTE_HIDDEN) &&
!(SMB_SEARCH_HIDDEN(sattr)))
return (ESRCH);
if ((attr.sa_dosattr & FILE_ATTRIBUTE_SYSTEM) &&
!(SMB_SEARCH_SYSTEM(sattr)))
return (ESRCH);
return (0);
}
static uint32_t
smb_rename_errno2status(int errnum)
{
static struct {
int errnum;
uint32_t status32;
} rc_map[] = {
{ EEXIST, NT_STATUS_OBJECT_NAME_COLLISION },
{ EPIPE, NT_STATUS_SHARING_VIOLATION },
{ ENOENT, NT_STATUS_OBJECT_NAME_NOT_FOUND },
{ ESRCH, NT_STATUS_NO_SUCH_FILE },
{ EINVAL, NT_STATUS_INVALID_PARAMETER },
{ EACCES, NT_STATUS_ACCESS_DENIED },
{ EISDIR, NT_STATUS_FILE_IS_A_DIRECTORY },
{ EIO, NT_STATUS_INTERNAL_ERROR }
};
int i;
if (errnum == 0)
return (0);
for (i = 0; i < sizeof (rc_map)/sizeof (rc_map[0]); ++i) {
if (rc_map[i].errnum == errnum) {
return (rc_map[i].status32);
}
}
return (smb_errno2status(errnum));
}