Commit 309c2b77 authored by Pali Rohár's avatar Pali Rohár Committed by Steve French
Browse files

cifs: Add support for creating reparse points over SMB1



SMB1 already supports querying reparse points and detecting types of
symlink, fifo, socket, block and char.

This change implements the missing part - ability to create a new reparse
points over SMB1. This includes everything which SMB2+ already supports:
- native SMB symlinks and sockets
- NFS style of special files (symlinks, fifos, sockets, char/block devs)
- WSL style of special files (symlinks, fifos, sockets, char/block devs)

Attaching a reparse point to an existing file or directory is done via
SMB1 SMB_COM_NT_TRANSACT/NT_TRANSACT_IOCTL/FSCTL_SET_REPARSE_POINT command
and implemented in a new cifs_create_reparse_inode() function.

This change introduce a new callback ->create_reparse_inode() which creates
a new reperse point file or directory and returns inode. For SMB1 it is
provided via that new cifs_create_reparse_inode() function.

Existing reparse.c code was only slightly updated to call new protocol
callback ->create_reparse_inode() instead of hardcoded SMB2+ function.
This make the whole reparse.c code to work with every SMB dialect.

The original callback ->create_reparse_symlink() is not needed anymore as
the implementation of new create_reparse_symlink() function is dialect
agnostic too. So the link.c code was updated to call that function directly
(and not via callback).

Signed-off-by: default avatarPali Rohár <pali@kernel.org>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 75d519b0
Loading
Loading
Loading
Loading
+8 −6
Original line number Diff line number Diff line
@@ -627,12 +627,14 @@ struct smb_version_operations {
	bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv);
	struct reparse_data_buffer * (*get_reparse_point_buffer)(const struct kvec *rsp_iov,
								 u32 *plen);
	int (*create_reparse_symlink)(const unsigned int xid,
				      struct inode *inode,
				      struct dentry *dentry,
	struct inode * (*create_reparse_inode)(struct cifs_open_info_data *data,
					       struct super_block *sb,
					       const unsigned int xid,
					       struct cifs_tcon *tcon,
					       const char *full_path,
				      const char *symname);
					       bool directory,
					       struct kvec *reparse_iov,
					       struct kvec *xattr_iov);
};

struct smb_version_values {
+8 −0
Original line number Diff line number Diff line
@@ -483,6 +483,14 @@ extern int cifs_query_reparse_point(const unsigned int xid,
				    const char *full_path,
				    u32 *tag, struct kvec *rsp,
				    int *rsp_buftype);
extern struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
					       struct super_block *sb,
					       const unsigned int xid,
					       struct cifs_tcon *tcon,
					       const char *full_path,
					       bool directory,
					       struct kvec *reparse_iov,
					       struct kvec *xattr_iov);
extern int CIFSSMB_set_compression(const unsigned int xid,
				   struct cifs_tcon *tcon, __u16 fid);
extern int CIFS_open(const unsigned int xid, struct cifs_open_parms *oparms,
+128 −0
Original line number Diff line number Diff line
@@ -2851,6 +2851,134 @@ int cifs_query_reparse_point(const unsigned int xid,
	return rc;
}

struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
					struct super_block *sb,
					const unsigned int xid,
					struct cifs_tcon *tcon,
					const char *full_path,
					bool directory,
					struct kvec *reparse_iov,
					struct kvec *xattr_iov)
{
	struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
	struct cifs_open_parms oparms;
	TRANSACT_IOCTL_REQ *io_req;
	struct inode *new = NULL;
	struct kvec in_iov[2];
	struct kvec out_iov;
	struct cifs_fid fid;
	int io_req_len;
	int oplock = 0;
	int buf_type = 0;
	int rc;

	cifs_tcon_dbg(FYI, "%s: path=%s\n", __func__, full_path);

	/*
	 * If server filesystem does not support reparse points then do not
	 * attempt to create reparse point. This will prevent creating unusable
	 * empty object on the server.
	 */
	if (!(le32_to_cpu(tcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS))
		return ERR_PTR(-EOPNOTSUPP);

#ifndef CONFIG_CIFS_XATTR
	if (xattr_iov)
		return ERR_PTR(-EOPNOTSUPP);
#endif

	oparms = CIFS_OPARMS(cifs_sb, tcon, full_path,
			     FILE_READ_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA,
			     FILE_CREATE,
			     (directory ? CREATE_NOT_FILE : CREATE_NOT_DIR) | OPEN_REPARSE_POINT,
			     ACL_NO_MODE);
	oparms.fid = &fid;

	rc = CIFS_open(xid, &oparms, &oplock, NULL);
	if (rc)
		return ERR_PTR(rc);

#ifdef CONFIG_CIFS_XATTR
	if (xattr_iov) {
		struct smb2_file_full_ea_info *ea;

		ea = &((struct smb2_create_ea_ctx *)xattr_iov->iov_base)->ea;
		while (1) {
			rc = CIFSSMBSetEA(xid,
					  tcon,
					  full_path,
					  &ea->ea_data[0],
					  &ea->ea_data[ea->ea_name_length+1],
					  le16_to_cpu(ea->ea_value_length),
					  cifs_sb->local_nls,
					  cifs_sb);
			if (rc)
				goto out_close;
			if (le32_to_cpu(ea->next_entry_offset) == 0)
				break;
			ea = (struct smb2_file_full_ea_info *)((u8 *)ea +
				le32_to_cpu(ea->next_entry_offset));
		}
	}
#endif

	rc = smb_init(SMB_COM_NT_TRANSACT, 23, tcon, (void **)&io_req, NULL);
	if (rc)
		goto out_close;

	inc_rfc1001_len(io_req, sizeof(io_req->Pad));

	io_req_len = be32_to_cpu(io_req->hdr.smb_buf_length) + sizeof(io_req->hdr.smb_buf_length);

	/* NT IOCTL response contains one-word long output setup buffer with size of output data. */
	io_req->MaxSetupCount = 1;
	/* NT IOCTL response does not contain output parameters. */
	io_req->MaxParameterCount = cpu_to_le32(0);
	/* FSCTL_SET_REPARSE_POINT response contains empty output data. */
	io_req->MaxDataCount = cpu_to_le32(0);

	io_req->TotalParameterCount = cpu_to_le32(0);
	io_req->TotalDataCount = cpu_to_le32(reparse_iov->iov_len);
	io_req->ParameterCount = io_req->TotalParameterCount;
	io_req->ParameterOffset = cpu_to_le32(0);
	io_req->DataCount = io_req->TotalDataCount;
	io_req->DataOffset = cpu_to_le32(offsetof(typeof(*io_req), Data) -
					 sizeof(io_req->hdr.smb_buf_length));
	io_req->SetupCount = 4;
	io_req->SubCommand = cpu_to_le16(NT_TRANSACT_IOCTL);
	io_req->FunctionCode = cpu_to_le32(FSCTL_SET_REPARSE_POINT);
	io_req->Fid = fid.netfid;
	io_req->IsFsctl = 1;
	io_req->IsRootFlag = 0;
	io_req->ByteCount = cpu_to_le16(le32_to_cpu(io_req->DataCount) + sizeof(io_req->Pad));

	inc_rfc1001_len(io_req, reparse_iov->iov_len);

	in_iov[0].iov_base = (char *)io_req;
	in_iov[0].iov_len = io_req_len;
	in_iov[1] = *reparse_iov;
	rc = SendReceive2(xid, tcon->ses, in_iov, ARRAY_SIZE(in_iov), &buf_type,
			  CIFS_NO_RSP_BUF, &out_iov);

	cifs_buf_release(io_req);

	if (!rc)
		rc = cifs_get_inode_info(&new, full_path, data, sb, xid, NULL);

out_close:
	CIFSSMBClose(xid, tcon, fid.netfid);

	/*
	 * If CREATE was successful but FSCTL_SET_REPARSE_POINT failed then
	 * remove the intermediate object created by CREATE. Otherwise
	 * empty object stay on the server when reparse call failed.
	 */
	if (rc)
		CIFSSMBDelFile(xid, tcon, full_path, cifs_sb, NULL);

	return rc ? ERR_PTR(rc) : new;
}

int
CIFSSMB_set_compression(const unsigned int xid, struct cifs_tcon *tcon,
		    __u16 fid)
+4 −9
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include "smb2proto.h"
#include "cifs_ioctl.h"
#include "fs_context.h"
#include "reparse.h"

/*
 * M-F Symlink Functions - Begin
@@ -570,7 +571,6 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
	int rc = -EOPNOTSUPP;
	unsigned int xid;
	struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
	struct TCP_Server_Info *server;
	struct tcon_link *tlink;
	struct cifs_tcon *pTcon;
	const char *full_path;
@@ -593,7 +593,6 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
		goto symlink_exit;
	}
	pTcon = tlink_tcon(tlink);
	server = cifs_pick_channel(pTcon->ses);

	full_path = build_path_from_dentry(direntry, page);
	if (IS_ERR(full_path)) {
@@ -643,13 +642,9 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
	case CIFS_SYMLINK_TYPE_NATIVE:
	case CIFS_SYMLINK_TYPE_NFS:
	case CIFS_SYMLINK_TYPE_WSL:
		if (server->ops->create_reparse_symlink &&
		    (le32_to_cpu(pTcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS)) {
			rc = server->ops->create_reparse_symlink(xid, inode,
								 direntry,
								 pTcon,
								 full_path,
								 symname);
		if (le32_to_cpu(pTcon->fsAttrInfo.Attributes) & FILE_SUPPORTS_REPARSE_POINTS) {
			rc = create_reparse_symlink(xid, inode, direntry, pTcon,
						    full_path, symname);
			goto symlink_exit;
		}
		break;
+10 −6
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
					   const char *symname,
					   bool *directory);

int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
int create_reparse_symlink(const unsigned int xid, struct inode *inode,
				struct dentry *dentry, struct cifs_tcon *tcon,
				const char *full_path, const char *symname)
{
@@ -227,7 +227,8 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,

	iov.iov_base = buf;
	iov.iov_len = len;
	new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
	new = tcon->ses->server->ops->create_reparse_inode(
				     &data, inode->i_sb, xid,
				     tcon, full_path, directory,
				     &iov, NULL);
	if (!IS_ERR(new))
@@ -399,7 +400,8 @@ static int create_native_socket(const unsigned int xid, struct inode *inode,
	struct inode *new;
	int rc = 0;

	new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
	new = tcon->ses->server->ops->create_reparse_inode(
				     &data, inode->i_sb, xid,
				     tcon, full_path, false, &iov, NULL);
	if (!IS_ERR(new))
		d_instantiate(dentry, new);
@@ -492,7 +494,8 @@ static int mknod_nfs(unsigned int xid, struct inode *inode,
		.symlink_target = kstrdup(symname, GFP_KERNEL),
	};

	new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
	new = tcon->ses->server->ops->create_reparse_inode(
				     &data, inode->i_sb, xid,
				     tcon, full_path, false, &iov, NULL);
	if (!IS_ERR(new))
		d_instantiate(dentry, new);
@@ -685,7 +688,8 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
	memcpy(data.wsl.eas, &cc->ea, len);
	data.wsl.eas_len = len;

	new = smb2_get_reparse_inode(&data, inode->i_sb,
	new = tcon->ses->server->ops->create_reparse_inode(
				     &data, inode->i_sb,
				     xid, tcon, full_path, false,
				     &reparse_iov, &xattr_iov);
	if (!IS_ERR(new))
@@ -698,7 +702,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
	return rc;
}

int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
int mknod_reparse(unsigned int xid, struct inode *inode,
		       struct dentry *dentry, struct cifs_tcon *tcon,
		       const char *full_path, umode_t mode, dev_t dev)
{
Loading