Skip to content

Commit 3eb4051

Browse files
paliSteve French
authored andcommitted
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 <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 8cf0b93 commit 3eb4051

File tree

3 files changed

+164
-4
lines changed

3 files changed

+164
-4
lines changed

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)