14
14
#include "fs_context.h"
15
15
#include "reparse.h"
16
16
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
+
17
23
int smb2_create_reparse_symlink (const unsigned int xid , struct inode * inode ,
18
24
struct dentry * dentry , struct cifs_tcon * tcon ,
19
25
const char * full_path , const char * symname )
@@ -24,6 +30,7 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
24
30
struct inode * new ;
25
31
struct kvec iov ;
26
32
__le16 * path ;
33
+ bool directory ;
27
34
char * sym , sep = CIFS_DIR_SEP (cifs_sb );
28
35
u16 len , plen ;
29
36
int rc = 0 ;
@@ -45,6 +52,18 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
45
52
goto out ;
46
53
}
47
54
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
+
48
67
plen = 2 * UniStrnlen ((wchar_t * )path , PATH_MAX );
49
68
len = sizeof (* buf ) + plen * 2 ;
50
69
buf = kzalloc (len , GFP_KERNEL );
@@ -69,7 +88,8 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
69
88
iov .iov_base = buf ;
70
89
iov .iov_len = len ;
71
90
new = smb2_get_reparse_inode (& data , inode -> i_sb , xid ,
72
- tcon , full_path , & iov , NULL );
91
+ tcon , full_path , directory ,
92
+ & iov , NULL );
73
93
if (!IS_ERR (new ))
74
94
d_instantiate (dentry , new );
75
95
else
@@ -81,6 +101,144 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
81
101
return rc ;
82
102
}
83
103
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
+
84
242
static int nfs_set_reparse_buf (struct reparse_posix_data * buf ,
85
243
mode_t mode , dev_t dev ,
86
244
struct kvec * iov )
@@ -137,7 +295,7 @@ static int mknod_nfs(unsigned int xid, struct inode *inode,
137
295
};
138
296
139
297
new = smb2_get_reparse_inode (& data , inode -> i_sb , xid ,
140
- tcon , full_path , & iov , NULL );
298
+ tcon , full_path , false, & iov , NULL );
141
299
if (!IS_ERR (new ))
142
300
d_instantiate (dentry , new );
143
301
else
@@ -283,7 +441,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode,
283
441
data .wsl .eas_len = len ;
284
442
285
443
new = smb2_get_reparse_inode (& data , inode -> i_sb ,
286
- xid , tcon , full_path ,
444
+ xid , tcon , full_path , false,
287
445
& reparse_iov , & xattr_iov );
288
446
if (!IS_ERR (new ))
289
447
d_instantiate (dentry , new );
0 commit comments