Skip to content

Commit cfea70e

Browse files
committed
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
2 parents ba01565 + 63271b7 commit cfea70e

File tree

4 files changed

+178
-7
lines changed

4 files changed

+178
-7
lines changed

fs/smb/client/cifs_unicode.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,21 @@ cifsConvertToUTF16(__le16 *target, const char *source, int srclen,
484484
/**
485485
* Remap spaces and periods found at the end of every
486486
* component of the path. The special cases of '.' and
487-
* '..' do not need to be dealt with explicitly because
488-
* they are addressed in namei.c:link_path_walk().
487+
* '..' are need to be handled because of symlinks.
488+
* They are treated as non-end-of-string to avoid
489+
* remapping and breaking symlinks pointing to . or ..
489490
**/
490-
if ((i == srclen - 1) || (source[i+1] == '\\'))
491+
if ((i == 0 || source[i-1] == '\\') &&
492+
source[i] == '.' &&
493+
(i == srclen-1 || source[i+1] == '\\'))
494+
end_of_string = false; /* "." case */
495+
else if (i >= 1 &&
496+
(i == 1 || source[i-2] == '\\') &&
497+
source[i-1] == '.' &&
498+
source[i] == '.' &&
499+
(i == srclen-1 || source[i+1] == '\\'))
500+
end_of_string = false; /* ".." case */
501+
else if ((i == srclen - 1) || (source[i+1] == '\\'))
491502
end_of_string = true;
492503
else
493504
end_of_string = false;

fs/smb/client/reparse.c

Lines changed: 161 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
#include "fs_context.h"
1515
#include "reparse.h"
1616

17+
static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
18+
const unsigned int xid,
19+
const char *full_path,
20+
const char *symname,
21+
bool *directory);
22+
1723
int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
1824
struct dentry *dentry, struct cifs_tcon *tcon,
1925
const char *full_path, const char *symname)
@@ -24,6 +30,7 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
2430
struct inode *new;
2531
struct kvec iov;
2632
__le16 *path;
33+
bool directory;
2734
char *sym, sep = CIFS_DIR_SEP(cifs_sb);
2835
u16 len, plen;
2936
int rc = 0;
@@ -45,6 +52,18 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
4552
goto out;
4653
}
4754

55+
/*
56+
* SMB distinguish between symlink to directory and symlink to file.
57+
* They cannot be exchanged (symlink of file type which points to
58+
* directory cannot be resolved and vice-versa). Try to detect if
59+
* the symlink target could be a directory or not. When detection
60+
* fails then treat symlink as a file (non-directory) symlink.
61+
*/
62+
directory = false;
63+
rc = detect_directory_symlink_target(cifs_sb, xid, full_path, symname, &directory);
64+
if (rc < 0)
65+
goto out;
66+
4867
plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX);
4968
len = sizeof(*buf) + plen * 2;
5069
buf = kzalloc(len, GFP_KERNEL);
@@ -69,7 +88,8 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
6988
iov.iov_base = buf;
7089
iov.iov_len = len;
7190
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
72-
tcon, full_path, &iov, NULL);
91+
tcon, full_path, directory,
92+
&iov, NULL);
7393
if (!IS_ERR(new))
7494
d_instantiate(dentry, new);
7595
else
@@ -81,6 +101,144 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
81101
return rc;
82102
}
83103

104+
static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
105+
const unsigned int xid,
106+
const char *full_path,
107+
const char *symname,
108+
bool *directory)
109+
{
110+
char sep = CIFS_DIR_SEP(cifs_sb);
111+
struct cifs_open_parms oparms;
112+
struct tcon_link *tlink;
113+
struct cifs_tcon *tcon;
114+
const char *basename;
115+
struct cifs_fid fid;
116+
char *resolved_path;
117+
int full_path_len;
118+
int basename_len;
119+
int symname_len;
120+
char *path_sep;
121+
__u32 oplock;
122+
int open_rc;
123+
124+
/*
125+
* First do some simple check. If the original Linux symlink target ends
126+
* with slash, or last path component is dot or dot-dot then it is for
127+
* sure symlink to the directory.
128+
*/
129+
basename = kbasename(symname);
130+
basename_len = strlen(basename);
131+
if (basename_len == 0 || /* symname ends with slash */
132+
(basename_len == 1 && basename[0] == '.') || /* last component is "." */
133+
(basename_len == 2 && basename[0] == '.' && basename[1] == '.')) { /* or ".." */
134+
*directory = true;
135+
return 0;
136+
}
137+
138+
/*
139+
* For absolute symlinks it is not possible to determinate
140+
* if it should point to directory or file.
141+
*/
142+
if (symname[0] == '/') {
143+
cifs_dbg(FYI,
144+
"%s: cannot determinate if the symlink target path '%s' "
145+
"is directory or not, creating '%s' as file symlink\n",
146+
__func__, symname, full_path);
147+
return 0;
148+
}
149+
150+
/*
151+
* If it was not detected as directory yet and the symlink is relative
152+
* then try to resolve the path on the SMB server, check if the path
153+
* exists and determinate if it is a directory or not.
154+
*/
155+
156+
full_path_len = strlen(full_path);
157+
symname_len = strlen(symname);
158+
159+
tlink = cifs_sb_tlink(cifs_sb);
160+
if (IS_ERR(tlink))
161+
return PTR_ERR(tlink);
162+
163+
resolved_path = kzalloc(full_path_len + symname_len + 1, GFP_KERNEL);
164+
if (!resolved_path) {
165+
cifs_put_tlink(tlink);
166+
return -ENOMEM;
167+
}
168+
169+
/*
170+
* Compose the resolved SMB symlink path from the SMB full path
171+
* and Linux target symlink path.
172+
*/
173+
memcpy(resolved_path, full_path, full_path_len+1);
174+
path_sep = strrchr(resolved_path, sep);
175+
if (path_sep)
176+
path_sep++;
177+
else
178+
path_sep = resolved_path;
179+
memcpy(path_sep, symname, symname_len+1);
180+
if (sep == '\\')
181+
convert_delimiter(path_sep, sep);
182+
183+
tcon = tlink_tcon(tlink);
184+
oparms = CIFS_OPARMS(cifs_sb, tcon, resolved_path,
185+
FILE_READ_ATTRIBUTES, FILE_OPEN, 0, ACL_NO_MODE);
186+
oparms.fid = &fid;
187+
188+
/* Try to open as a directory (NOT_FILE) */
189+
oplock = 0;
190+
oparms.create_options = cifs_create_options(cifs_sb,
191+
CREATE_NOT_FILE | OPEN_REPARSE_POINT);
192+
open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL);
193+
if (open_rc == 0) {
194+
/* Successful open means that the target path is definitely a directory. */
195+
*directory = true;
196+
tcon->ses->server->ops->close(xid, tcon, &fid);
197+
} else if (open_rc == -ENOTDIR) {
198+
/* -ENOTDIR means that the target path is definitely a file. */
199+
*directory = false;
200+
} else if (open_rc == -ENOENT) {
201+
/* -ENOENT means that the target path does not exist. */
202+
cifs_dbg(FYI,
203+
"%s: symlink target path '%s' does not exist, "
204+
"creating '%s' as file symlink\n",
205+
__func__, symname, full_path);
206+
} else {
207+
/* Try to open as a file (NOT_DIR) */
208+
oplock = 0;
209+
oparms.create_options = cifs_create_options(cifs_sb,
210+
CREATE_NOT_DIR | OPEN_REPARSE_POINT);
211+
open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL);
212+
if (open_rc == 0) {
213+
/* Successful open means that the target path is definitely a file. */
214+
*directory = false;
215+
tcon->ses->server->ops->close(xid, tcon, &fid);
216+
} else if (open_rc == -EISDIR) {
217+
/* -EISDIR means that the target path is definitely a directory. */
218+
*directory = true;
219+
} else {
220+
/*
221+
* This code branch is called when we do not have a permission to
222+
* open the resolved_path or some other client/process denied
223+
* opening the resolved_path.
224+
*
225+
* TODO: Try to use ops->query_dir_first on the parent directory
226+
* of resolved_path, search for basename of resolved_path and
227+
* check if the ATTR_DIRECTORY is set in fi.Attributes. In some
228+
* case this could work also when opening of the path is denied.
229+
*/
230+
cifs_dbg(FYI,
231+
"%s: cannot determinate if the symlink target path '%s' "
232+
"is directory or not, creating '%s' as file symlink\n",
233+
__func__, symname, full_path);
234+
}
235+
}
236+
237+
kfree(resolved_path);
238+
cifs_put_tlink(tlink);
239+
return 0;
240+
}
241+
84242
static int nfs_set_reparse_buf(struct reparse_posix_data *buf,
85243
mode_t mode, dev_t dev,
86244
struct kvec *iov)
@@ -137,7 +295,7 @@ static int mknod_nfs(unsigned int xid, struct inode *inode,
137295
};
138296

139297
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
140-
tcon, full_path, &iov, NULL);
298+
tcon, full_path, false, &iov, NULL);
141299
if (!IS_ERR(new))
142300
d_instantiate(dentry, new);
143301
else
@@ -283,7 +441,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
283441
data.wsl.eas_len = len;
284442

285443
new = smb2_get_reparse_inode(&data, inode->i_sb,
286-
xid, tcon, full_path,
444+
xid, tcon, full_path, false,
287445
&reparse_iov, &xattr_iov);
288446
if (!IS_ERR(new))
289447
d_instantiate(dentry, new);

fs/smb/client/smb2inode.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
11981198
const unsigned int xid,
11991199
struct cifs_tcon *tcon,
12001200
const char *full_path,
1201+
bool directory,
12011202
struct kvec *reparse_iov,
12021203
struct kvec *xattr_iov)
12031204
{
@@ -1217,7 +1218,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
12171218
FILE_READ_ATTRIBUTES |
12181219
FILE_WRITE_ATTRIBUTES,
12191220
FILE_CREATE,
1220-
CREATE_NOT_DIR | OPEN_REPARSE_POINT,
1221+
(directory ? CREATE_NOT_FILE : CREATE_NOT_DIR) | OPEN_REPARSE_POINT,
12211222
ACL_NO_MODE);
12221223
if (xattr_iov)
12231224
oparms.ea_cctx = xattr_iov;

fs/smb/client/smb2proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
6161
const unsigned int xid,
6262
struct cifs_tcon *tcon,
6363
const char *full_path,
64+
bool directory,
6465
struct kvec *reparse_iov,
6566
struct kvec *xattr_iov);
6667
int smb2_query_reparse_point(const unsigned int xid,

0 commit comments

Comments
 (0)