1 From 71f33f7378dd4656783b83ab086106f7453d104d Mon Sep 17 00:00:00 2001
2 From: Sasha Levin <sashal@kernel.org>
3 Date: Tue, 2 Apr 2024 10:11:35 +0100
4 Subject: cifs: Fix caching to try to do open O_WRONLY as rdwr on server
6 From: David Howells <dhowells@redhat.com>
8 [ Upstream commit e9e62243a3e2322cf639f653a0b0a88a76446ce7 ]
10 When we're engaged in local caching of a cifs filesystem, we cannot perform
11 caching of a partially written cache granule unless we can read the rest of
12 the granule. This can result in unexpected access errors being reported to
15 Fix this by the following: if a file is opened O_WRONLY locally, but the
16 mount was given the "-o fsc" flag, try first opening the remote file with
17 GENERIC_READ|GENERIC_WRITE and if that returns -EACCES, try dropping the
18 GENERIC_READ and doing the open again. If that last succeeds, invalidate
19 the cache for that file as for O_DIRECT.
21 Fixes: 70431bfd825d ("cifs: Support fscache indexing rewrite")
22 Signed-off-by: David Howells <dhowells@redhat.com>
23 cc: Steve French <sfrench@samba.org>
24 cc: Shyam Prasad N <nspmangalore@gmail.com>
25 cc: Rohith Surabattula <rohiths.msft@gmail.com>
26 cc: Jeff Layton <jlayton@kernel.org>
27 cc: linux-cifs@vger.kernel.org
28 cc: netfs@lists.linux.dev
29 cc: linux-fsdevel@vger.kernel.org
30 Signed-off-by: Steve French <stfrench@microsoft.com>
31 Signed-off-by: Sasha Levin <sashal@kernel.org>
33 fs/smb/client/dir.c | 15 +++++++++++++
34 fs/smb/client/file.c | 48 ++++++++++++++++++++++++++++++++---------
35 fs/smb/client/fscache.h | 6 ++++++
36 3 files changed, 59 insertions(+), 10 deletions(-)
38 diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
39 index e382b794acbed..863c7bc3db86f 100644
40 --- a/fs/smb/client/dir.c
41 +++ b/fs/smb/client/dir.c
42 @@ -180,6 +180,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
44 struct TCP_Server_Info *server = tcon->ses->server;
45 struct cifs_open_parms oparms;
46 + int rdwr_for_fscache = 0;
49 if (tcon->ses->server->oplocks)
50 @@ -191,6 +192,10 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
51 return PTR_ERR(full_path);
54 + /* If we're caching, we need to be able to fill in around partial writes. */
55 + if (cifs_fscache_enabled(inode) && (oflags & O_ACCMODE) == O_WRONLY)
56 + rdwr_for_fscache = 1;
58 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
59 if (tcon->unix_ext && cap_unix(tcon->ses) && !tcon->broken_posix_open &&
60 (CIFS_UNIX_POSIX_PATH_OPS_CAP &
61 @@ -267,6 +272,8 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
62 desired_access |= GENERIC_READ; /* is this too little? */
63 if (OPEN_FMODE(oflags) & FMODE_WRITE)
64 desired_access |= GENERIC_WRITE;
65 + if (rdwr_for_fscache == 1)
66 + desired_access |= GENERIC_READ;
68 disposition = FILE_OVERWRITE_IF;
69 if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
70 @@ -295,6 +302,7 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
71 if (!tcon->unix_ext && (mode & S_IWUGO) == 0)
72 create_options |= CREATE_OPTION_READONLY;
75 oparms = (struct cifs_open_parms) {
78 @@ -308,8 +316,15 @@ static int cifs_do_create(struct inode *inode, struct dentry *direntry, unsigned
79 rc = server->ops->open(xid, &oparms, oplock, buf);
81 cifs_dbg(FYI, "cifs_create returned 0x%x\n", rc);
82 + if (rc == -EACCES && rdwr_for_fscache == 1) {
83 + desired_access &= ~GENERIC_READ;
84 + rdwr_for_fscache = 2;
89 + if (rdwr_for_fscache == 2)
90 + cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE);
92 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
94 diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
95 index 0f3405e0f2e48..c240cea7ca349 100644
96 --- a/fs/smb/client/file.c
97 +++ b/fs/smb/client/file.c
98 @@ -77,12 +77,12 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
102 -static inline int cifs_convert_flags(unsigned int flags)
103 +static inline int cifs_convert_flags(unsigned int flags, int rdwr_for_fscache)
105 if ((flags & O_ACCMODE) == O_RDONLY)
107 else if ((flags & O_ACCMODE) == O_WRONLY)
108 - return GENERIC_WRITE;
109 + return rdwr_for_fscache == 1 ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_WRITE;
110 else if ((flags & O_ACCMODE) == O_RDWR) {
111 /* GENERIC_ALL is too much permission to request
112 can cause unnecessary access denied on create */
113 @@ -219,11 +219,16 @@ static int cifs_nt_open(const char *full_path, struct inode *inode, struct cifs_
114 int create_options = CREATE_NOT_DIR;
115 struct TCP_Server_Info *server = tcon->ses->server;
116 struct cifs_open_parms oparms;
117 + int rdwr_for_fscache = 0;
119 if (!server->ops->open)
122 - desired_access = cifs_convert_flags(f_flags);
123 + /* If we're caching, we need to be able to fill in around partial writes. */
124 + if (cifs_fscache_enabled(inode) && (f_flags & O_ACCMODE) == O_WRONLY)
125 + rdwr_for_fscache = 1;
127 + desired_access = cifs_convert_flags(f_flags, rdwr_for_fscache);
129 /*********************************************************************
130 * open flag mapping table:
131 @@ -260,6 +265,7 @@ static int cifs_nt_open(const char *full_path, struct inode *inode, struct cifs_
132 if (f_flags & O_DIRECT)
133 create_options |= CREATE_NO_BUFFER;
136 oparms = (struct cifs_open_parms) {
139 @@ -271,8 +277,16 @@ static int cifs_nt_open(const char *full_path, struct inode *inode, struct cifs_
142 rc = server->ops->open(xid, &oparms, oplock, buf);
145 + if (rc == -EACCES && rdwr_for_fscache == 1) {
146 + desired_access = cifs_convert_flags(f_flags, 0);
147 + rdwr_for_fscache = 2;
152 + if (rdwr_for_fscache == 2)
153 + cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE);
155 /* TODO: Add support for calling posix query info but with passing in fid */
157 @@ -705,11 +719,11 @@ int cifs_open(struct inode *inode, struct file *file)
159 fscache_use_cookie(cifs_inode_cookie(file_inode(file)),
160 file->f_mode & FMODE_WRITE);
161 - if (file->f_flags & O_DIRECT &&
162 - (!((file->f_flags & O_ACCMODE) != O_RDONLY) ||
163 - file->f_flags & O_APPEND))
164 - cifs_invalidate_cache(file_inode(file),
165 - FSCACHE_INVAL_DIO_WRITE);
166 + if (!(file->f_flags & O_DIRECT))
168 + if ((file->f_flags & (O_ACCMODE | O_APPEND)) == O_RDONLY)
170 + cifs_invalidate_cache(file_inode(file), FSCACHE_INVAL_DIO_WRITE);
173 free_dentry_path(page);
174 @@ -774,6 +788,7 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
175 int disposition = FILE_OPEN;
176 int create_options = CREATE_NOT_DIR;
177 struct cifs_open_parms oparms;
178 + int rdwr_for_fscache = 0;
181 mutex_lock(&cfile->fh_mutex);
182 @@ -837,7 +852,11 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
184 #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
186 - desired_access = cifs_convert_flags(cfile->f_flags);
187 + /* If we're caching, we need to be able to fill in around partial writes. */
188 + if (cifs_fscache_enabled(inode) && (cfile->f_flags & O_ACCMODE) == O_WRONLY)
189 + rdwr_for_fscache = 1;
191 + desired_access = cifs_convert_flags(cfile->f_flags, rdwr_for_fscache);
193 /* O_SYNC also has bit for O_DSYNC so following check picks up either */
194 if (cfile->f_flags & O_SYNC)
195 @@ -849,6 +868,7 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
196 if (server->ops->get_lease_key)
197 server->ops->get_lease_key(inode, &cfile->fid);
200 oparms = (struct cifs_open_parms) {
203 @@ -874,6 +894,11 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
204 /* indicate that we need to relock the file */
205 oparms.reconnect = true;
207 + if (rc == -EACCES && rdwr_for_fscache == 1) {
208 + desired_access = cifs_convert_flags(cfile->f_flags, 0);
209 + rdwr_for_fscache = 2;
214 mutex_unlock(&cfile->fh_mutex);
215 @@ -882,6 +907,9 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
216 goto reopen_error_exit;
219 + if (rdwr_for_fscache == 2)
220 + cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE);
222 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
224 #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
225 diff --git a/fs/smb/client/fscache.h b/fs/smb/client/fscache.h
226 index 67b601041f0a3..c691b98b442a6 100644
227 --- a/fs/smb/client/fscache.h
228 +++ b/fs/smb/client/fscache.h
229 @@ -108,6 +108,11 @@ static inline void cifs_readpage_to_fscache(struct inode *inode,
230 __cifs_readpage_to_fscache(inode, page);
233 +static inline bool cifs_fscache_enabled(struct inode *inode)
235 + return fscache_cookie_enabled(cifs_inode_cookie(inode));
238 #else /* CONFIG_CIFS_FSCACHE */
240 void cifs_fscache_fill_coherency(struct inode *inode,
241 @@ -123,6 +128,7 @@ static inline void cifs_fscache_release_inode_cookie(struct inode *inode) {}
242 static inline void cifs_fscache_unuse_inode_cookie(struct inode *inode, bool update) {}
243 static inline struct fscache_cookie *cifs_inode_cookie(struct inode *inode) { return NULL; }
244 static inline void cifs_invalidate_cache(struct inode *inode, unsigned int flags) {}
245 +static inline bool cifs_fscache_enabled(struct inode *inode) { return false; }
247 static inline int cifs_fscache_query_occupancy(struct inode *inode,
248 pgoff_t first, unsigned int nr_pages,