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+
1723int 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+
84242static 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 );
0 commit comments