linux/fs/smb/client/reparse.c
Pali Rohár 3eb4051253 cifs: Improve creating native symlinks pointing to directory
SMB protocol for native symlinks distinguish between symlink to directory
and symlink to file. These two symlink types cannot be exchanged, which
means that symlink of file type pointing to directory cannot be resolved at
all (and vice-versa).

Windows follows this rule for local filesystems (NTFS) and also for SMB.

Linux SMB client currenly creates all native symlinks of file type. Which
means that Windows (and some other SMB clients) cannot resolve symlinks
pointing to directory created by Linux SMB client.

As Linux system does not distinguish between directory and file symlinks,
its API does not provide enough information for Linux SMB client during
creating of native symlinks.

Add some heuristic into the Linux SMB client for choosing the correct
symlink type during symlink creation. Check if the symlink target location
ends with slash, or last path component is dot or dot-dot, and check if the
target location on SMB share exists and is a directory. If at least one
condition is truth then create a new SMB symlink of directory type.
Otherwise create it as file type symlink.

This change improves interoperability with Windows systems. Windows systems
would be able to resolve more SMB symlinks created by Linux SMB client
which points to existing directory.

Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
2024-10-06 22:57:12 -05:00

733 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 Paulo Alcantara <pc@manguebit.com>
*/
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include "cifsglob.h"
#include "smb2proto.h"
#include "cifsproto.h"
#include "cifs_unicode.h"
#include "cifs_debug.h"
#include "fs_context.h"
#include "reparse.h"
static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
const unsigned int xid,
const char *full_path,
const char *symname,
bool *directory);
int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, const char *symname)
{
struct reparse_symlink_data_buffer *buf = NULL;
struct cifs_open_info_data data;
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
struct inode *new;
struct kvec iov;
__le16 *path;
bool directory;
char *sym, sep = CIFS_DIR_SEP(cifs_sb);
u16 len, plen;
int rc = 0;
sym = kstrdup(symname, GFP_KERNEL);
if (!sym)
return -ENOMEM;
data = (struct cifs_open_info_data) {
.reparse_point = true,
.reparse = { .tag = IO_REPARSE_TAG_SYMLINK, },
.symlink_target = sym,
};
convert_delimiter(sym, sep);
path = cifs_convert_path_to_utf16(sym, cifs_sb);
if (!path) {
rc = -ENOMEM;
goto out;
}
/*
* SMB distinguish between symlink to directory and symlink to file.
* They cannot be exchanged (symlink of file type which points to
* directory cannot be resolved and vice-versa). Try to detect if
* the symlink target could be a directory or not. When detection
* fails then treat symlink as a file (non-directory) symlink.
*/
directory = false;
rc = detect_directory_symlink_target(cifs_sb, xid, full_path, symname, &directory);
if (rc < 0)
goto out;
plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX);
len = sizeof(*buf) + plen * 2;
buf = kzalloc(len, GFP_KERNEL);
if (!buf) {
rc = -ENOMEM;
goto out;
}
buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_SYMLINK);
buf->ReparseDataLength = cpu_to_le16(len - sizeof(struct reparse_data_buffer));
buf->SubstituteNameOffset = cpu_to_le16(plen);
buf->SubstituteNameLength = cpu_to_le16(plen);
memcpy(&buf->PathBuffer[plen], path, plen);
buf->PrintNameOffset = 0;
buf->PrintNameLength = cpu_to_le16(plen);
memcpy(buf->PathBuffer, path, plen);
buf->Flags = cpu_to_le32(*symname != '/' ? SYMLINK_FLAG_RELATIVE : 0);
if (*sym != sep)
buf->Flags = cpu_to_le32(SYMLINK_FLAG_RELATIVE);
convert_delimiter(sym, '/');
iov.iov_base = buf;
iov.iov_len = len;
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
tcon, full_path, directory,
&iov, NULL);
if (!IS_ERR(new))
d_instantiate(dentry, new);
else
rc = PTR_ERR(new);
out:
kfree(path);
cifs_free_open_info(&data);
kfree(buf);
return rc;
}
static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
const unsigned int xid,
const char *full_path,
const char *symname,
bool *directory)
{
char sep = CIFS_DIR_SEP(cifs_sb);
struct cifs_open_parms oparms;
struct tcon_link *tlink;
struct cifs_tcon *tcon;
const char *basename;
struct cifs_fid fid;
char *resolved_path;
int full_path_len;
int basename_len;
int symname_len;
char *path_sep;
__u32 oplock;
int open_rc;
/*
* First do some simple check. If the original Linux symlink target ends
* with slash, or last path component is dot or dot-dot then it is for
* sure symlink to the directory.
*/
basename = kbasename(symname);
basename_len = strlen(basename);
if (basename_len == 0 || /* symname ends with slash */
(basename_len == 1 && basename[0] == '.') || /* last component is "." */
(basename_len == 2 && basename[0] == '.' && basename[1] == '.')) { /* or ".." */
*directory = true;
return 0;
}
/*
* For absolute symlinks it is not possible to determinate
* if it should point to directory or file.
*/
if (symname[0] == '/') {
cifs_dbg(FYI,
"%s: cannot determinate if the symlink target path '%s' "
"is directory or not, creating '%s' as file symlink\n",
__func__, symname, full_path);
return 0;
}
/*
* If it was not detected as directory yet and the symlink is relative
* then try to resolve the path on the SMB server, check if the path
* exists and determinate if it is a directory or not.
*/
full_path_len = strlen(full_path);
symname_len = strlen(symname);
tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink))
return PTR_ERR(tlink);
resolved_path = kzalloc(full_path_len + symname_len + 1, GFP_KERNEL);
if (!resolved_path) {
cifs_put_tlink(tlink);
return -ENOMEM;
}
/*
* Compose the resolved SMB symlink path from the SMB full path
* and Linux target symlink path.
*/
memcpy(resolved_path, full_path, full_path_len+1);
path_sep = strrchr(resolved_path, sep);
if (path_sep)
path_sep++;
else
path_sep = resolved_path;
memcpy(path_sep, symname, symname_len+1);
if (sep == '\\')
convert_delimiter(path_sep, sep);
tcon = tlink_tcon(tlink);
oparms = CIFS_OPARMS(cifs_sb, tcon, resolved_path,
FILE_READ_ATTRIBUTES, FILE_OPEN, 0, ACL_NO_MODE);
oparms.fid = &fid;
/* Try to open as a directory (NOT_FILE) */
oplock = 0;
oparms.create_options = cifs_create_options(cifs_sb,
CREATE_NOT_FILE | OPEN_REPARSE_POINT);
open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL);
if (open_rc == 0) {
/* Successful open means that the target path is definitely a directory. */
*directory = true;
tcon->ses->server->ops->close(xid, tcon, &fid);
} else if (open_rc == -ENOTDIR) {
/* -ENOTDIR means that the target path is definitely a file. */
*directory = false;
} else if (open_rc == -ENOENT) {
/* -ENOENT means that the target path does not exist. */
cifs_dbg(FYI,
"%s: symlink target path '%s' does not exist, "
"creating '%s' as file symlink\n",
__func__, symname, full_path);
} else {
/* Try to open as a file (NOT_DIR) */
oplock = 0;
oparms.create_options = cifs_create_options(cifs_sb,
CREATE_NOT_DIR | OPEN_REPARSE_POINT);
open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL);
if (open_rc == 0) {
/* Successful open means that the target path is definitely a file. */
*directory = false;
tcon->ses->server->ops->close(xid, tcon, &fid);
} else if (open_rc == -EISDIR) {
/* -EISDIR means that the target path is definitely a directory. */
*directory = true;
} else {
/*
* This code branch is called when we do not have a permission to
* open the resolved_path or some other client/process denied
* opening the resolved_path.
*
* TODO: Try to use ops->query_dir_first on the parent directory
* of resolved_path, search for basename of resolved_path and
* check if the ATTR_DIRECTORY is set in fi.Attributes. In some
* case this could work also when opening of the path is denied.
*/
cifs_dbg(FYI,
"%s: cannot determinate if the symlink target path '%s' "
"is directory or not, creating '%s' as file symlink\n",
__func__, symname, full_path);
}
}
kfree(resolved_path);
cifs_put_tlink(tlink);
return 0;
}
static int nfs_set_reparse_buf(struct reparse_posix_data *buf,
mode_t mode, dev_t dev,
struct kvec *iov)
{
u64 type;
u16 len, dlen;
len = sizeof(*buf);
switch ((type = reparse_mode_nfs_type(mode))) {
case NFS_SPECFILE_BLK:
case NFS_SPECFILE_CHR:
dlen = sizeof(__le64);
break;
case NFS_SPECFILE_FIFO:
case NFS_SPECFILE_SOCK:
dlen = 0;
break;
default:
return -EOPNOTSUPP;
}
buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_NFS);
buf->Reserved = 0;
buf->InodeType = cpu_to_le64(type);
buf->ReparseDataLength = cpu_to_le16(len + dlen -
sizeof(struct reparse_data_buffer));
*(__le64 *)buf->DataBuffer = cpu_to_le64(((u64)MINOR(dev) << 32) |
MAJOR(dev));
iov->iov_base = buf;
iov->iov_len = len + dlen;
return 0;
}
static int mknod_nfs(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
{
struct cifs_open_info_data data;
struct reparse_posix_data *p;
struct inode *new;
struct kvec iov;
__u8 buf[sizeof(*p) + sizeof(__le64)];
int rc;
p = (struct reparse_posix_data *)buf;
rc = nfs_set_reparse_buf(p, mode, dev, &iov);
if (rc)
return rc;
data = (struct cifs_open_info_data) {
.reparse_point = true,
.reparse = { .tag = IO_REPARSE_TAG_NFS, .posix = p, },
};
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
tcon, full_path, false, &iov, NULL);
if (!IS_ERR(new))
d_instantiate(dentry, new);
else
rc = PTR_ERR(new);
cifs_free_open_info(&data);
return rc;
}
static int wsl_set_reparse_buf(struct reparse_data_buffer *buf,
mode_t mode, struct kvec *iov)
{
u32 tag;
switch ((tag = reparse_mode_wsl_tag(mode))) {
case IO_REPARSE_TAG_LX_BLK:
case IO_REPARSE_TAG_LX_CHR:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_AF_UNIX:
break;
default:
return -EOPNOTSUPP;
}
buf->ReparseTag = cpu_to_le32(tag);
buf->Reserved = 0;
buf->ReparseDataLength = 0;
iov->iov_base = buf;
iov->iov_len = sizeof(*buf);
return 0;
}
static struct smb2_create_ea_ctx *ea_create_context(u32 dlen, size_t *cc_len)
{
struct smb2_create_ea_ctx *cc;
*cc_len = round_up(sizeof(*cc) + dlen, 8);
cc = kzalloc(*cc_len, GFP_KERNEL);
if (!cc)
return ERR_PTR(-ENOMEM);
cc->ctx.NameOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx,
name));
cc->ctx.NameLength = cpu_to_le16(4);
memcpy(cc->name, SMB2_CREATE_EA_BUFFER, strlen(SMB2_CREATE_EA_BUFFER));
cc->ctx.DataOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx, ea));
cc->ctx.DataLength = cpu_to_le32(dlen);
return cc;
}
struct wsl_xattr {
const char *name;
__le64 value;
u16 size;
u32 next;
};
static int wsl_set_xattrs(struct inode *inode, umode_t _mode,
dev_t _dev, struct kvec *iov)
{
struct smb2_file_full_ea_info *ea;
struct smb2_create_ea_ctx *cc;
struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx;
__le64 uid = cpu_to_le64(from_kuid(current_user_ns(), ctx->linux_uid));
__le64 gid = cpu_to_le64(from_kgid(current_user_ns(), ctx->linux_gid));
__le64 dev = cpu_to_le64(((u64)MINOR(_dev) << 32) | MAJOR(_dev));
__le64 mode = cpu_to_le64(_mode);
struct wsl_xattr xattrs[] = {
{ .name = SMB2_WSL_XATTR_UID, .value = uid, .size = SMB2_WSL_XATTR_UID_SIZE, },
{ .name = SMB2_WSL_XATTR_GID, .value = gid, .size = SMB2_WSL_XATTR_GID_SIZE, },
{ .name = SMB2_WSL_XATTR_MODE, .value = mode, .size = SMB2_WSL_XATTR_MODE_SIZE, },
{ .name = SMB2_WSL_XATTR_DEV, .value = dev, .size = SMB2_WSL_XATTR_DEV_SIZE, },
};
size_t cc_len;
u32 dlen = 0, next = 0;
int i, num_xattrs;
u8 name_size = SMB2_WSL_XATTR_NAME_LEN + 1;
memset(iov, 0, sizeof(*iov));
/* Exclude $LXDEV xattr for sockets and fifos */
if (S_ISSOCK(_mode) || S_ISFIFO(_mode))
num_xattrs = ARRAY_SIZE(xattrs) - 1;
else
num_xattrs = ARRAY_SIZE(xattrs);
for (i = 0; i < num_xattrs; i++) {
xattrs[i].next = ALIGN(sizeof(*ea) + name_size +
xattrs[i].size, 4);
dlen += xattrs[i].next;
}
cc = ea_create_context(dlen, &cc_len);
if (IS_ERR(cc))
return PTR_ERR(cc);
ea = &cc->ea;
for (i = 0; i < num_xattrs; i++) {
ea = (void *)((u8 *)ea + next);
next = xattrs[i].next;
ea->next_entry_offset = cpu_to_le32(next);
ea->ea_name_length = name_size - 1;
ea->ea_value_length = cpu_to_le16(xattrs[i].size);
memcpy(ea->ea_data, xattrs[i].name, name_size);
memcpy(&ea->ea_data[name_size],
&xattrs[i].value, xattrs[i].size);
}
ea->next_entry_offset = 0;
iov->iov_base = cc;
iov->iov_len = cc_len;
return 0;
}
static int mknod_wsl(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
{
struct cifs_open_info_data data;
struct reparse_data_buffer buf;
struct smb2_create_ea_ctx *cc;
struct inode *new;
unsigned int len;
struct kvec reparse_iov, xattr_iov;
int rc;
rc = wsl_set_reparse_buf(&buf, mode, &reparse_iov);
if (rc)
return rc;
rc = wsl_set_xattrs(inode, mode, dev, &xattr_iov);
if (rc)
return rc;
data = (struct cifs_open_info_data) {
.reparse_point = true,
.reparse = { .tag = le32_to_cpu(buf.ReparseTag), .buf = &buf, },
};
cc = xattr_iov.iov_base;
len = le32_to_cpu(cc->ctx.DataLength);
memcpy(data.wsl.eas, &cc->ea, len);
data.wsl.eas_len = len;
new = smb2_get_reparse_inode(&data, inode->i_sb,
xid, tcon, full_path, false,
&reparse_iov, &xattr_iov);
if (!IS_ERR(new))
d_instantiate(dentry, new);
else
rc = PTR_ERR(new);
cifs_free_open_info(&data);
kfree(xattr_iov.iov_base);
return rc;
}
int smb2_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)
{
struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx;
int rc = -EOPNOTSUPP;
switch (ctx->reparse_type) {
case CIFS_REPARSE_TYPE_NFS:
rc = mknod_nfs(xid, inode, dentry, tcon, full_path, mode, dev);
break;
case CIFS_REPARSE_TYPE_WSL:
rc = mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev);
break;
}
return rc;
}
/* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */
static int parse_reparse_posix(struct reparse_posix_data *buf,
struct cifs_sb_info *cifs_sb,
struct cifs_open_info_data *data)
{
unsigned int len;
u64 type;
len = le16_to_cpu(buf->ReparseDataLength);
if (len < sizeof(buf->InodeType)) {
cifs_dbg(VFS, "srv returned malformed nfs buffer\n");
return -EIO;
}
len -= sizeof(buf->InodeType);
switch ((type = le64_to_cpu(buf->InodeType))) {
case NFS_SPECFILE_LNK:
if (len == 0 || (len % 2)) {
cifs_dbg(VFS, "srv returned malformed nfs symlink buffer\n");
return -EIO;
}
/*
* Check that buffer does not contain UTF-16 null codepoint
* because Linux cannot process symlink with null byte.
*/
if (UniStrnlen((wchar_t *)buf->DataBuffer, len/2) != len/2) {
cifs_dbg(VFS, "srv returned null byte in nfs symlink target location\n");
return -EIO;
}
data->symlink_target = cifs_strndup_from_utf16(buf->DataBuffer,
len, true,
cifs_sb->local_nls);
if (!data->symlink_target)
return -ENOMEM;
cifs_dbg(FYI, "%s: target path: %s\n",
__func__, data->symlink_target);
break;
case NFS_SPECFILE_CHR:
case NFS_SPECFILE_BLK:
/* DataBuffer for block and char devices contains two 32-bit numbers */
if (len != 8) {
cifs_dbg(VFS, "srv returned malformed nfs buffer for type: 0x%llx\n", type);
return -EIO;
}
break;
case NFS_SPECFILE_FIFO:
case NFS_SPECFILE_SOCK:
/* DataBuffer for fifos and sockets is empty */
if (len != 0) {
cifs_dbg(VFS, "srv returned malformed nfs buffer for type: 0x%llx\n", type);
return -EIO;
}
break;
default:
cifs_dbg(VFS, "%s: unhandled inode type: 0x%llx\n",
__func__, type);
return -EOPNOTSUPP;
}
return 0;
}
static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
u32 plen, bool unicode,
struct cifs_sb_info *cifs_sb,
struct cifs_open_info_data *data)
{
unsigned int len;
unsigned int offs;
/* We handle Symbolic Link reparse tag here. See: MS-FSCC 2.1.2.4 */
offs = le16_to_cpu(sym->SubstituteNameOffset);
len = le16_to_cpu(sym->SubstituteNameLength);
if (offs + 20 > plen || offs + len + 20 > plen) {
cifs_dbg(VFS, "srv returned malformed symlink buffer\n");
return -EIO;
}
data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs,
len, unicode,
cifs_sb->local_nls);
if (!data->symlink_target)
return -ENOMEM;
convert_delimiter(data->symlink_target, '/');
cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target);
return 0;
}
int parse_reparse_point(struct reparse_data_buffer *buf,
u32 plen, struct cifs_sb_info *cifs_sb,
bool unicode, struct cifs_open_info_data *data)
{
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
data->reparse.buf = buf;
/* See MS-FSCC 2.1.2 */
switch (le32_to_cpu(buf->ReparseTag)) {
case IO_REPARSE_TAG_NFS:
return parse_reparse_posix((struct reparse_posix_data *)buf,
cifs_sb, data);
case IO_REPARSE_TAG_SYMLINK:
return parse_reparse_symlink(
(struct reparse_symlink_data_buffer *)buf,
plen, unicode, cifs_sb, data);
case IO_REPARSE_TAG_LX_SYMLINK:
case IO_REPARSE_TAG_AF_UNIX:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_LX_CHR:
case IO_REPARSE_TAG_LX_BLK:
break;
default:
cifs_tcon_dbg(VFS | ONCE, "unhandled reparse tag: 0x%08x\n",
le32_to_cpu(buf->ReparseTag));
break;
}
return 0;
}
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
struct kvec *rsp_iov,
struct cifs_open_info_data *data)
{
struct reparse_data_buffer *buf;
struct smb2_ioctl_rsp *io = rsp_iov->iov_base;
u32 plen = le32_to_cpu(io->OutputCount);
buf = (struct reparse_data_buffer *)((u8 *)io +
le32_to_cpu(io->OutputOffset));
return parse_reparse_point(buf, plen, cifs_sb, true, data);
}
static void wsl_to_fattr(struct cifs_open_info_data *data,
struct cifs_sb_info *cifs_sb,
u32 tag, struct cifs_fattr *fattr)
{
struct smb2_file_full_ea_info *ea;
u32 next = 0;
switch (tag) {
case IO_REPARSE_TAG_LX_SYMLINK:
fattr->cf_mode |= S_IFLNK;
break;
case IO_REPARSE_TAG_LX_FIFO:
fattr->cf_mode |= S_IFIFO;
break;
case IO_REPARSE_TAG_AF_UNIX:
fattr->cf_mode |= S_IFSOCK;
break;
case IO_REPARSE_TAG_LX_CHR:
fattr->cf_mode |= S_IFCHR;
break;
case IO_REPARSE_TAG_LX_BLK:
fattr->cf_mode |= S_IFBLK;
break;
}
if (!data->wsl.eas_len)
goto out;
ea = (struct smb2_file_full_ea_info *)data->wsl.eas;
do {
const char *name;
void *v;
u8 nlen;
ea = (void *)((u8 *)ea + next);
next = le32_to_cpu(ea->next_entry_offset);
if (!le16_to_cpu(ea->ea_value_length))
continue;
name = ea->ea_data;
nlen = ea->ea_name_length;
v = (void *)((u8 *)ea->ea_data + ea->ea_name_length + 1);
if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen))
fattr->cf_uid = wsl_make_kuid(cifs_sb, v);
else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen))
fattr->cf_gid = wsl_make_kgid(cifs_sb, v);
else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen))
fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v);
else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen))
fattr->cf_rdev = reparse_mkdev(v);
} while (next);
out:
fattr->cf_dtype = S_DT(fattr->cf_mode);
}
bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
struct cifs_fattr *fattr,
struct cifs_open_info_data *data)
{
struct reparse_posix_data *buf = data->reparse.posix;
u32 tag = data->reparse.tag;
if (tag == IO_REPARSE_TAG_NFS && buf) {
if (le16_to_cpu(buf->ReparseDataLength) < sizeof(buf->InodeType))
return false;
switch (le64_to_cpu(buf->InodeType)) {
case NFS_SPECFILE_CHR:
if (le16_to_cpu(buf->ReparseDataLength) != sizeof(buf->InodeType) + 8)
return false;
fattr->cf_mode |= S_IFCHR;
fattr->cf_rdev = reparse_mkdev(buf->DataBuffer);
break;
case NFS_SPECFILE_BLK:
if (le16_to_cpu(buf->ReparseDataLength) != sizeof(buf->InodeType) + 8)
return false;
fattr->cf_mode |= S_IFBLK;
fattr->cf_rdev = reparse_mkdev(buf->DataBuffer);
break;
case NFS_SPECFILE_FIFO:
fattr->cf_mode |= S_IFIFO;
break;
case NFS_SPECFILE_SOCK:
fattr->cf_mode |= S_IFSOCK;
break;
case NFS_SPECFILE_LNK:
fattr->cf_mode |= S_IFLNK;
break;
default:
WARN_ON_ONCE(1);
return false;
}
goto out;
}
switch (tag) {
case IO_REPARSE_TAG_INTERNAL:
if (!(fattr->cf_cifsattrs & ATTR_DIRECTORY))
return false;
fallthrough;
case IO_REPARSE_TAG_DFS:
case IO_REPARSE_TAG_DFSR:
case IO_REPARSE_TAG_MOUNT_POINT:
/* See cifs_create_junction_fattr() */
fattr->cf_mode = S_IFDIR | 0711;
break;
case IO_REPARSE_TAG_LX_SYMLINK:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_AF_UNIX:
case IO_REPARSE_TAG_LX_CHR:
case IO_REPARSE_TAG_LX_BLK:
wsl_to_fattr(data, cifs_sb, tag, fattr);
break;
case 0: /* SMB1 symlink */
case IO_REPARSE_TAG_SYMLINK:
case IO_REPARSE_TAG_NFS:
fattr->cf_mode |= S_IFLNK;
break;
default:
return false;
}
out:
fattr->cf_dtype = S_DT(fattr->cf_mode);
return true;
}