mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-16 13:34:30 +00:00
two fixes for Windows symlink handling
-----BEGIN PGP SIGNATURE----- iQGzBAABCgAdFiEE6fsu8pdIjtWE/DpLiiy9cAdyT1EFAmcMBf4ACgkQiiy9cAdy T1Hf7Qv/f/TEXZisWIGshUpIerxOAWmN70bTw4sNID9ge8mVWwtVJBs57rlSjPTc 97Jj95urqnKEAGk/KC8qntp5QCMBQAeBFILigZph2c7vqEXPQy0dpbDUEUFuRN2G mq0wn7IcJZcPJmhZGx9JJeteHk/24drJRSM+jyklwI2Rmev6Y6dlsv4JyMuvP7iI YuCdbN7rYXsRBkpnK5AbiWCRdxwQMiMuGsppNQyBVSZKkt/g+8R16Z6WKxSbkaZf XajVsywhlP5Bg9HRAk/YTPK4enKVi8ISp9qfS9EuinwM/VFzEnXnYrec/fiD0Ukg rEemM7iF/YQdQq/2q8gm5KpoOjnLbaew+Zb+OoWyXMK7RJygD79+uMHn3v1cdi7B BWCgbQQ7KiRi6rOo0Xzz8Rmw3L4+DHjTvIbh46jz90qQyuumR2hUSa7cPl2ATO4l lxA50Q8xPE1i0Cfob1w/XHlrfmWMyovtHSKDvaeOMclp/VAHDfS6nB0x/ngyY8UH ii2czaDd =uI8y -----END PGP SIGNATURE----- Merge tag '6.12-rc2-cifs-fixes' of git://git.samba.org/sfrench/cifs-2.6 Pull smb client fixes from Steve French: "Two fixes for Windows symlink handling" * tag '6.12-rc2-cifs-fixes' of git://git.samba.org/sfrench/cifs-2.6: cifs: Fix creating native symlinks pointing to current or parent directory cifs: Improve creating native symlinks pointing to directory
This commit is contained in:
commit
cfea70e835
@ -484,10 +484,21 @@ cifsConvertToUTF16(__le16 *target, const char *source, int srclen,
|
||||
/**
|
||||
* Remap spaces and periods found at the end of every
|
||||
* component of the path. The special cases of '.' and
|
||||
* '..' do not need to be dealt with explicitly because
|
||||
* they are addressed in namei.c:link_path_walk().
|
||||
* '..' are need to be handled because of symlinks.
|
||||
* They are treated as non-end-of-string to avoid
|
||||
* remapping and breaking symlinks pointing to . or ..
|
||||
**/
|
||||
if ((i == srclen - 1) || (source[i+1] == '\\'))
|
||||
if ((i == 0 || source[i-1] == '\\') &&
|
||||
source[i] == '.' &&
|
||||
(i == srclen-1 || source[i+1] == '\\'))
|
||||
end_of_string = false; /* "." case */
|
||||
else if (i >= 1 &&
|
||||
(i == 1 || source[i-2] == '\\') &&
|
||||
source[i-1] == '.' &&
|
||||
source[i] == '.' &&
|
||||
(i == srclen-1 || source[i+1] == '\\'))
|
||||
end_of_string = false; /* ".." case */
|
||||
else if ((i == srclen - 1) || (source[i+1] == '\\'))
|
||||
end_of_string = true;
|
||||
else
|
||||
end_of_string = false;
|
||||
|
@ -14,6 +14,12 @@
|
||||
#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)
|
||||
@ -24,6 +30,7 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
|
||||
struct inode *new;
|
||||
struct kvec iov;
|
||||
__le16 *path;
|
||||
bool directory;
|
||||
char *sym, sep = CIFS_DIR_SEP(cifs_sb);
|
||||
u16 len, plen;
|
||||
int rc = 0;
|
||||
@ -45,6 +52,18 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
|
||||
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);
|
||||
@ -69,7 +88,8 @@ int smb2_create_reparse_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,
|
||||
tcon, full_path, &iov, NULL);
|
||||
tcon, full_path, directory,
|
||||
&iov, NULL);
|
||||
if (!IS_ERR(new))
|
||||
d_instantiate(dentry, new);
|
||||
else
|
||||
@ -81,6 +101,144 @@ out:
|
||||
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)
|
||||
@ -137,7 +295,7 @@ static int mknod_nfs(unsigned int xid, struct inode *inode,
|
||||
};
|
||||
|
||||
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
|
||||
tcon, full_path, &iov, NULL);
|
||||
tcon, full_path, false, &iov, NULL);
|
||||
if (!IS_ERR(new))
|
||||
d_instantiate(dentry, new);
|
||||
else
|
||||
@ -283,7 +441,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
|
||||
data.wsl.eas_len = len;
|
||||
|
||||
new = smb2_get_reparse_inode(&data, inode->i_sb,
|
||||
xid, tcon, full_path,
|
||||
xid, tcon, full_path, false,
|
||||
&reparse_iov, &xattr_iov);
|
||||
if (!IS_ERR(new))
|
||||
d_instantiate(dentry, new);
|
||||
|
@ -1198,6 +1198,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
|
||||
const unsigned int xid,
|
||||
struct cifs_tcon *tcon,
|
||||
const char *full_path,
|
||||
bool directory,
|
||||
struct kvec *reparse_iov,
|
||||
struct kvec *xattr_iov)
|
||||
{
|
||||
@ -1217,7 +1218,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
|
||||
FILE_READ_ATTRIBUTES |
|
||||
FILE_WRITE_ATTRIBUTES,
|
||||
FILE_CREATE,
|
||||
CREATE_NOT_DIR | OPEN_REPARSE_POINT,
|
||||
(directory ? CREATE_NOT_FILE : CREATE_NOT_DIR) | OPEN_REPARSE_POINT,
|
||||
ACL_NO_MODE);
|
||||
if (xattr_iov)
|
||||
oparms.ea_cctx = xattr_iov;
|
||||
|
@ -61,6 +61,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
|
||||
const unsigned int xid,
|
||||
struct cifs_tcon *tcon,
|
||||
const char *full_path,
|
||||
bool directory,
|
||||
struct kvec *reparse_iov,
|
||||
struct kvec *xattr_iov);
|
||||
int smb2_query_reparse_point(const unsigned int xid,
|
||||
|
Loading…
x
Reference in New Issue
Block a user