Skip to content

Commit

Permalink
smb: client: allow creating special files via reparse points
Browse files Browse the repository at this point in the history
Add support for creating special files (e.g. char/block devices,
sockets, fifos) via NFS reparse points on SMB2+, which are fully
supported by most SMB servers and documented in MS-FSCC.

smb2_get_reparse_inode() creates the file with a corresponding reparse
point buffer set in @iov through a single roundtrip to the server.

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202311260746.HOJ039BV-lkp@intel.com/
Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
Paulo Alcantara authored and Steve French committed Jan 7, 2024
1 parent 3322960 commit 102466f
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 60 deletions.
8 changes: 6 additions & 2 deletions fs/smb/client/cifsproto.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,12 @@ int cifs_get_inode_info(struct inode **inode, const char *full_path,
bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
struct cifs_fattr *fattr,
struct cifs_open_info_data *data);
extern int smb311_posix_get_inode_info(struct inode **pinode, const char *search_path,
struct super_block *sb, unsigned int xid);

extern int smb311_posix_get_inode_info(struct inode **inode,
const char *full_path,
struct cifs_open_info_data *data,
struct super_block *sb,
const unsigned int xid);
extern int cifs_get_inode_info_unix(struct inode **pinode,
const unsigned char *search_path,
struct super_block *sb, unsigned int xid);
Expand Down
7 changes: 4 additions & 3 deletions fs/smb/client/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -680,9 +680,10 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry,
full_path, d_inode(direntry));

again:
if (pTcon->posix_extensions)
rc = smb311_posix_get_inode_info(&newInode, full_path, parent_dir_inode->i_sb, xid);
else if (pTcon->unix_ext) {
if (pTcon->posix_extensions) {
rc = smb311_posix_get_inode_info(&newInode, full_path, NULL,
parent_dir_inode->i_sb, xid);
} else if (pTcon->unix_ext) {
rc = cifs_get_inode_info_unix(&newInode, full_path,
parent_dir_inode->i_sb, xid);
} else {
Expand Down
10 changes: 6 additions & 4 deletions fs/smb/client/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1020,14 +1020,16 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
if (!is_interrupt_error(rc))
mapping_set_error(inode->i_mapping, rc);

if (tcon->posix_extensions)
rc = smb311_posix_get_inode_info(&inode, full_path, inode->i_sb, xid);
else if (tcon->unix_ext)
if (tcon->posix_extensions) {
rc = smb311_posix_get_inode_info(&inode, full_path,
NULL, inode->i_sb, xid);
} else if (tcon->unix_ext) {
rc = cifs_get_inode_info_unix(&inode, full_path,
inode->i_sb, xid);
else
} else {
rc = cifs_get_inode_info(&inode, full_path, NULL,
inode->i_sb, xid, NULL);
}
}
/*
* Else we are writing out data to server already and could deadlock if
Expand Down
76 changes: 50 additions & 26 deletions fs/smb/client/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,9 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
const unsigned int xid,
struct cifs_tcon *tcon,
const char *full_path,
struct cifs_fattr *fattr)
struct cifs_fattr *fattr,
struct cifs_sid *owner,
struct cifs_sid *group)
{
struct TCP_Server_Info *server = tcon->ses->server;
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
Expand Down Expand Up @@ -1092,7 +1094,8 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
rc = 0;
goto out;
default:
if (data->symlink_target) {
/* Check for cached reparse point data */
if (data->symlink_target || data->reparse.buf) {
rc = 0;
} else if (server->ops->parse_reparse_point) {
rc = server->ops->parse_reparse_point(cifs_sb,
Expand All @@ -1101,7 +1104,10 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
break;
}

cifs_open_info_to_fattr(fattr, data, sb);
if (tcon->posix_extensions)
smb311_posix_info_to_fattr(fattr, data, owner, group, sb);
else
cifs_open_info_to_fattr(fattr, data, sb);
out:
free_rsp_buf(rsp_buftype, rsp_iov.iov_base);
return rc;
Expand Down Expand Up @@ -1152,7 +1158,8 @@ static int cifs_get_fattr(struct cifs_open_info_data *data,
*/
if (cifs_open_data_reparse(data)) {
rc = reparse_info_to_fattr(data, sb, xid, tcon,
full_path, fattr);
full_path, fattr,
NULL, NULL);
} else {
cifs_open_info_to_fattr(fattr, data, sb);
}
Expand Down Expand Up @@ -1290,39 +1297,49 @@ int cifs_get_inode_info(struct inode **inode,
return rc;
}

static int smb311_posix_get_fattr(struct cifs_fattr *fattr,
static int smb311_posix_get_fattr(struct cifs_open_info_data *data,
struct cifs_fattr *fattr,
const char *full_path,
struct super_block *sb,
const unsigned int xid)
{
struct cifs_open_info_data data = {};
struct cifs_open_info_data tmp_data = {};
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
struct cifs_tcon *tcon;
struct tcon_link *tlink;
struct cifs_sid owner, group;
int tmprc;
int rc;
int rc = 0;

tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink))
return PTR_ERR(tlink);
tcon = tlink_tcon(tlink);

/*
* 1. Fetch file metadata
* 1. Fetch file metadata if not provided (data)
*/

rc = smb311_posix_query_path_info(xid, tcon, cifs_sb,
full_path, &data,
&owner, &group);
if (!data) {
rc = smb311_posix_query_path_info(xid, tcon, cifs_sb,
full_path, &tmp_data,
&owner, &group);
data = &tmp_data;
}

/*
* 2. Convert it to internal cifs metadata (fattr)
*/

switch (rc) {
case 0:
smb311_posix_info_to_fattr(fattr, &data, &owner, &group, sb);
if (cifs_open_data_reparse(data)) {
rc = reparse_info_to_fattr(data, sb, xid, tcon,
full_path, fattr,
&owner, &group);
} else {
smb311_posix_info_to_fattr(fattr, data,
&owner, &group, sb);
}
break;
case -EREMOTE:
/* DFS link, no metadata available on this server */
Expand Down Expand Up @@ -1353,12 +1370,15 @@ static int smb311_posix_get_fattr(struct cifs_fattr *fattr,

out:
cifs_put_tlink(tlink);
cifs_free_open_info(&data);
cifs_free_open_info(data);
return rc;
}

int smb311_posix_get_inode_info(struct inode **inode, const char *full_path,
struct super_block *sb, const unsigned int xid)
int smb311_posix_get_inode_info(struct inode **inode,
const char *full_path,
struct cifs_open_info_data *data,
struct super_block *sb,
const unsigned int xid)
{
struct cifs_fattr fattr = {};
int rc;
Expand All @@ -1368,7 +1388,7 @@ int smb311_posix_get_inode_info(struct inode **inode, const char *full_path,
return 0;
}

rc = smb311_posix_get_fattr(&fattr, full_path, sb, xid);
rc = smb311_posix_get_fattr(data, &fattr, full_path, sb, xid);
if (rc)
goto out;

Expand Down Expand Up @@ -1516,7 +1536,7 @@ struct inode *cifs_root_iget(struct super_block *sb)

convert_delimiter(path, CIFS_DIR_SEP(cifs_sb));
if (tcon->posix_extensions)
rc = smb311_posix_get_fattr(&fattr, path, sb, xid);
rc = smb311_posix_get_fattr(NULL, &fattr, path, sb, xid);
else
rc = cifs_get_fattr(NULL, sb, xid, NULL, &fattr, &inode, path);

Expand Down Expand Up @@ -1889,16 +1909,18 @@ cifs_mkdir_qinfo(struct inode *parent, struct dentry *dentry, umode_t mode,
int rc = 0;
struct inode *inode = NULL;

if (tcon->posix_extensions)
rc = smb311_posix_get_inode_info(&inode, full_path, parent->i_sb, xid);
if (tcon->posix_extensions) {
rc = smb311_posix_get_inode_info(&inode, full_path,
NULL, parent->i_sb, xid);
#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
else if (tcon->unix_ext)
} else if (tcon->unix_ext) {
rc = cifs_get_inode_info_unix(&inode, full_path, parent->i_sb,
xid);
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
else
} else {
rc = cifs_get_inode_info(&inode, full_path, NULL, parent->i_sb,
xid, NULL);
}

if (rc)
return rc;
Expand Down Expand Up @@ -2579,13 +2601,15 @@ int cifs_revalidate_dentry_attr(struct dentry *dentry)
dentry, cifs_get_time(dentry), jiffies);

again:
if (cifs_sb_master_tcon(CIFS_SB(sb))->posix_extensions)
rc = smb311_posix_get_inode_info(&inode, full_path, sb, xid);
else if (cifs_sb_master_tcon(CIFS_SB(sb))->unix_ext)
if (cifs_sb_master_tcon(CIFS_SB(sb))->posix_extensions) {
rc = smb311_posix_get_inode_info(&inode, full_path,
NULL, sb, xid);
} else if (cifs_sb_master_tcon(CIFS_SB(sb))->unix_ext) {
rc = cifs_get_inode_info_unix(&inode, full_path, sb, xid);
else
} else {
rc = cifs_get_inode_info(&inode, full_path, NULL, sb,
xid, NULL);
}
if (rc == -EAGAIN && count++ < 10)
goto again;
out:
Expand Down
10 changes: 6 additions & 4 deletions fs/smb/client/link.c
Original file line number Diff line number Diff line change
Expand Up @@ -614,14 +614,16 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
cifs_sb_target->local_nls); */

if (rc == 0) {
if (pTcon->posix_extensions)
rc = smb311_posix_get_inode_info(&newinode, full_path, inode->i_sb, xid);
else if (pTcon->unix_ext)
if (pTcon->posix_extensions) {
rc = smb311_posix_get_inode_info(&newinode, full_path,
NULL, inode->i_sb, xid);
} else if (pTcon->unix_ext) {
rc = cifs_get_inode_info_unix(&newinode, full_path,
inode->i_sb, xid);
else
} else {
rc = cifs_get_inode_info(&newinode, full_path, NULL,
inode->i_sb, xid, NULL);
}

if (rc != 0) {
cifs_dbg(FYI, "Create symlink ok, getinodeinfo fail rc = %d\n",
Expand Down
25 changes: 14 additions & 11 deletions fs/smb/client/smb2glob.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@
* Identifiers for functions that use the open, operation, close pattern
* in smb2inode.c:smb2_compound_op()
*/
#define SMB2_OP_SET_DELETE 1
#define SMB2_OP_SET_INFO 2
#define SMB2_OP_QUERY_INFO 3
#define SMB2_OP_QUERY_DIR 4
#define SMB2_OP_MKDIR 5
#define SMB2_OP_RENAME 6
#define SMB2_OP_DELETE 7
#define SMB2_OP_HARDLINK 8
#define SMB2_OP_SET_EOF 9
#define SMB2_OP_RMDIR 10
#define SMB2_OP_POSIX_QUERY_INFO 11
enum smb2_compound_ops {
SMB2_OP_SET_DELETE = 1,
SMB2_OP_SET_INFO,
SMB2_OP_QUERY_INFO,
SMB2_OP_QUERY_DIR,
SMB2_OP_MKDIR,
SMB2_OP_RENAME,
SMB2_OP_DELETE,
SMB2_OP_HARDLINK,
SMB2_OP_SET_EOF,
SMB2_OP_RMDIR,
SMB2_OP_POSIX_QUERY_INFO,
SMB2_OP_SET_REPARSE
};

/* Used when constructing chained read requests. */
#define CHAINED_REQUEST 1
Expand Down
75 changes: 75 additions & 0 deletions fs/smb/client/smb2inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,22 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
smb2_set_related(&rqst[num_rqst++]);
trace_smb3_hardlink_enter(xid, ses->Suid, tcon->tid, full_path);
break;
case SMB2_OP_SET_REPARSE:
rqst[num_rqst].rq_iov = vars->io_iov;
rqst[num_rqst].rq_nvec = ARRAY_SIZE(vars->io_iov);

rc = SMB2_ioctl_init(tcon, server, &rqst[num_rqst],
COMPOUND_FID, COMPOUND_FID,
FSCTL_SET_REPARSE_POINT,
in_iov[i].iov_base,
in_iov[i].iov_len, 0);
if (rc)
goto finished;
smb2_set_next_command(tcon, &rqst[num_rqst]);
smb2_set_related(&rqst[num_rqst++]);
trace_smb3_set_reparse_compound_enter(xid, ses->Suid,
tcon->tid, full_path);
break;
default:
cifs_dbg(VFS, "Invalid command\n");
rc = -EINVAL;
Expand Down Expand Up @@ -503,6 +519,16 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
tcon->tid);
SMB2_set_info_free(&rqst[num_rqst++]);
break;
case SMB2_OP_SET_REPARSE:
if (rc) {
trace_smb3_set_reparse_compound_err(xid, ses->Suid,
tcon->tid, rc);
} else {
trace_smb3_set_reparse_compound_done(xid, ses->Suid,
tcon->tid);
}
SMB2_ioctl_free(&rqst[num_rqst++]);
break;
}
}
SMB2_close_free(&rqst[num_rqst]);
Expand Down Expand Up @@ -887,3 +913,52 @@ smb2_set_file_info(struct inode *inode, const char *full_path,
cifs_put_tlink(tlink);
return rc;
}

struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
struct super_block *sb,
const unsigned int xid,
struct cifs_tcon *tcon,
const char *full_path,
struct kvec *iov)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
struct cifsFileInfo *cfile;
struct inode *new = NULL;
struct kvec in_iov[2];
int cmds[2];
int da, co, cd;
int rc;

da = SYNCHRONIZE | DELETE |
FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES;
co = CREATE_NOT_DIR | OPEN_REPARSE_POINT;
cd = FILE_CREATE;
cmds[0] = SMB2_OP_SET_REPARSE;
in_iov[0] = *iov;
in_iov[1].iov_base = data;
in_iov[1].iov_len = sizeof(*data);

if (tcon->posix_extensions) {
cmds[1] = SMB2_OP_POSIX_QUERY_INFO;
cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
da, cd, co, ACL_NO_MODE, in_iov,
cmds, 2, cfile, NULL, NULL, NULL, NULL);
if (!rc) {
rc = smb311_posix_get_inode_info(&new, full_path,
data, sb, xid);
}
} else {
cmds[1] = SMB2_OP_QUERY_INFO;
cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
da, cd, co, ACL_NO_MODE, in_iov,
cmds, 2, cfile, NULL, NULL, NULL, NULL);
if (!rc) {
rc = cifs_get_inode_info(&new, full_path,
data, sb, xid, NULL);
}
}
return rc ? ERR_PTR(rc) : new;
}
Loading

0 comments on commit 102466f

Please sign in to comment.