e8dd8a287f
BugLink: https://bugs.launchpad.net/bugs/2101915 [ Upstream commit 723f4ef90452aa629f3d923e92e0449d69362b1d ] SMB symlink which has SYMLINK_FLAG_RELATIVE set is relative (as opposite of the absolute) and it can be relative either to the current directory (where is the symlink stored) or relative to the top level export path. To what it is relative depends on the first character of the symlink target path. If the first character is path separator then symlink is relative to the export, otherwise to the current directory. Linux (and generally POSIX systems) supports only symlink paths relative to the current directory where is symlink stored. Currently if Linux SMB client reads relative SMB symlink with first character as path separator (slash), it let as is. Which means that Linux interpret it as absolute symlink pointing from the root (/). But this location is different than the top level directory of SMB export (unless SMB export was mounted to the root) and thefore SMB symlinks relative to the export are interpreted wrongly by Linux SMB client. Fix this problem. As Linux does not have equivalent of the path relative to the top of the mount point, convert such symlink target path relative to the current directory. Do this by prepending "../" pattern N times before the SMB target path, where N is the number of path separators found in SMB symlink path. So for example, if SMB share is mounted to Linux path /mnt/share/, symlink is stored in file /mnt/share/test/folder1/symlink (so SMB symlink path is test\folder1\symlink) and SMB symlink target points to \test\folder2\file, then convert symlink target path to Linux path ../../test/folder2/file. Deduplicate code for parsing SMB symlinks in native form from functions smb2_parse_symlink_response() and parse_reparse_native_symlink() into new function smb2_parse_native_symlink() and pass into this new function a new full_path parameter from callers, which specify SMB full path where is symlink stored. This change fixes resolving of the native Windows symlinks relative to the top level directory of the SMB share. Signed-off-by: Pali Rohár <pali@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com> Stable-dep-of: f4ca4f5a36ea ("cifs: Fix parsing reparse point with native symlink in SMB1 non-UNICODE session") Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Koichiro Den <koichiro.den@canonical.com> Signed-off-by: Stefan Bader <stefan.bader@canonical.com>
93 lines
2.8 KiB
C
93 lines
2.8 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Copyright (c) 2024 Paulo Alcantara <pc@manguebit.com>
|
|
*/
|
|
|
|
#ifndef _CIFS_REPARSE_H
|
|
#define _CIFS_REPARSE_H
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/stat.h>
|
|
#include "cifsglob.h"
|
|
|
|
#define REPARSE_SYM_PATH_MAX 4060
|
|
|
|
/*
|
|
* Used only by cifs.ko to ignore reparse points from files when client or
|
|
* server doesn't support FSCTL_GET_REPARSE_POINT.
|
|
*/
|
|
#define IO_REPARSE_TAG_INTERNAL ((__u32)~0U)
|
|
|
|
static inline dev_t reparse_nfs_mkdev(struct reparse_posix_data *buf)
|
|
{
|
|
u64 v = le64_to_cpu(*(__le64 *)buf->DataBuffer);
|
|
|
|
return MKDEV(v >> 32, v & 0xffffffff);
|
|
}
|
|
|
|
static inline u64 reparse_mode_nfs_type(mode_t mode)
|
|
{
|
|
switch (mode & S_IFMT) {
|
|
case S_IFBLK: return NFS_SPECFILE_BLK;
|
|
case S_IFCHR: return NFS_SPECFILE_CHR;
|
|
case S_IFIFO: return NFS_SPECFILE_FIFO;
|
|
case S_IFSOCK: return NFS_SPECFILE_SOCK;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Match a reparse point inode if reparse tag and ctime haven't changed.
|
|
*
|
|
* Windows Server updates ctime of reparse points when their data have changed.
|
|
* The server doesn't allow changing reparse tags from existing reparse points,
|
|
* though it's worth checking.
|
|
*/
|
|
static inline bool reparse_inode_match(struct inode *inode,
|
|
struct cifs_fattr *fattr)
|
|
{
|
|
struct cifsInodeInfo *cinode = CIFS_I(inode);
|
|
struct timespec64 ctime = inode_get_ctime(inode);
|
|
|
|
/*
|
|
* Do not match reparse tags when client or server doesn't support
|
|
* FSCTL_GET_REPARSE_POINT. @fattr->cf_cifstag should contain correct
|
|
* reparse tag from query dir response but the client won't be able to
|
|
* read the reparse point data anyway. This spares us a revalidation.
|
|
*/
|
|
if (cinode->reparse_tag != IO_REPARSE_TAG_INTERNAL &&
|
|
cinode->reparse_tag != fattr->cf_cifstag)
|
|
return false;
|
|
return (cinode->cifsAttrs & ATTR_REPARSE) &&
|
|
timespec64_equal(&ctime, &fattr->cf_ctime);
|
|
}
|
|
|
|
static inline bool cifs_open_data_reparse(struct cifs_open_info_data *data)
|
|
{
|
|
struct smb2_file_all_info *fi = &data->fi;
|
|
u32 attrs = le32_to_cpu(fi->Attributes);
|
|
bool ret;
|
|
|
|
ret = data->reparse_point || (attrs & ATTR_REPARSE);
|
|
if (ret)
|
|
attrs |= ATTR_REPARSE;
|
|
fi->Attributes = cpu_to_le32(attrs);
|
|
return ret;
|
|
}
|
|
|
|
bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
|
|
struct cifs_fattr *fattr,
|
|
struct cifs_open_info_data *data);
|
|
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);
|
|
int smb2_make_nfs_node(unsigned int xid, struct inode *inode,
|
|
struct dentry *dentry, struct cifs_tcon *tcon,
|
|
const char *full_path, umode_t mode, dev_t dev);
|
|
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb,
|
|
const char *full_path,
|
|
struct kvec *rsp_iov,
|
|
struct cifs_open_info_data *data);
|
|
|
|
#endif /* _CIFS_REPARSE_H */
|