1 /* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
2
3 /*
4 These tests were done with various Linux 2.6 kernels, FreeBSD 6.2 and
5 Solaris 8 and 10.
6
7 Attribute cache is usually flushed with chown()ing or fchown()ing the file.
8 The safest way would be to use uid=-1 gid=-1, but this doesn't work with
9 Linux (it does with FreeBSD 6.2 and Solaris). So we'll first get the
10 file's owner and use it. As long as we're not root the file's owner can't
11 change accidentally. If would be possible to also use chmod()/fchmod(), but
12 that's riskier since it could actually cause an unwanted change.
13
14 Write cache can be flushed with fdatasync(). It's all we need, but other
15 tested alternatives are: fcntl locking (Linux 2.6, Solaris),
16 fchown() (Solaris) and dup()+close() (Linux 2.6, Solaris).
17
18 Read cache flushing is more problematic. There's no universal way to do it.
19 The working methods are:
20
21 Linux 2.6: fcntl(), O_DIRECT
22 Solaris: fchown(), fcntl(), dup()+close()
23 FreeBSD 6.2: fchown()
24
25 fchown() can be easily used for Solaris and FreeBSD, but Linux requires
26 playing with locks. O_DIRECT requires CONFIG_NFS_DIRECTIO to be enabled, so
27 we can't always use it.
28 */
29
30 #include "lib.h"
31 #include "path-util.h"
32 #include "nfs-workarounds.h"
33
34 #include <fcntl.h>
35 #include <unistd.h>
36 #include <sys/stat.h>
37
38 #if defined (__linux__) || defined(__sun)
39 # define READ_CACHE_FLUSH_FCNTL
40 #endif
41 #if defined(__FreeBSD__) || defined(__sun)
42 # define ATTRCACHE_FLUSH_CHOWN_UID_1
43 #endif
44
45 static void nfs_flush_file_handle_cache_parent_dir(const char *path);
46
47 static int
nfs_safe_do(const char * path,int (* callback)(const char * path,void * context),void * context)48 nfs_safe_do(const char *path, int (*callback)(const char *path, void *context),
49 void *context)
50 {
51 unsigned int i;
52 int ret;
53
54 for (i = 1;; i++) {
55 ret = callback(path, context);
56 if (ret == 0 || errno != ESTALE || i == NFS_ESTALE_RETRY_COUNT)
57 break;
58
59 /* ESTALE: Some operating systems may fail with this if they
60 can't internally revalidate the NFS file handle. Flush the
61 file handle and try again */
62 nfs_flush_file_handle_cache(path);
63 }
64 return ret;
65 }
66
67 struct nfs_safe_open_context {
68 int flags;
69 int fd;
70 };
71
nfs_safe_open_callback(const char * path,void * context)72 static int nfs_safe_open_callback(const char *path, void *context)
73 {
74 struct nfs_safe_open_context *ctx = context;
75
76 ctx->fd = open(path, ctx->flags);
77 return ctx->fd == -1 ? -1 : 0;
78 }
79
nfs_safe_open(const char * path,int flags)80 int nfs_safe_open(const char *path, int flags)
81 {
82 struct nfs_safe_open_context ctx;
83
84 i_assert((flags & O_CREAT) == 0);
85
86 ctx.flags = flags;
87 if (nfs_safe_do(path, nfs_safe_open_callback, &ctx) < 0)
88 return -1;
89
90 return ctx.fd;
91 }
92
nfs_safe_stat_callback(const char * path,void * context)93 static int nfs_safe_stat_callback(const char *path, void *context)
94 {
95 struct stat *buf = context;
96
97 return stat(path, buf);
98 }
99
nfs_safe_stat(const char * path,struct stat * buf)100 int nfs_safe_stat(const char *path, struct stat *buf)
101 {
102 return nfs_safe_do(path, nfs_safe_stat_callback, buf);
103 }
104
nfs_safe_lstat_callback(const char * path,void * context)105 static int nfs_safe_lstat_callback(const char *path, void *context)
106 {
107 struct stat *buf = context;
108
109 return lstat(path, buf);
110 }
111
nfs_safe_lstat(const char * path,struct stat * buf)112 int nfs_safe_lstat(const char *path, struct stat *buf)
113 {
114 return nfs_safe_do(path, nfs_safe_lstat_callback, buf);
115 }
116
nfs_safe_link(const char * oldpath,const char * newpath,bool links1)117 int nfs_safe_link(const char *oldpath, const char *newpath, bool links1)
118 {
119 struct stat st;
120 nlink_t orig_link_count = 1;
121
122 if (!links1) {
123 if (stat(oldpath, &st) < 0)
124 return -1;
125 orig_link_count = st.st_nlink;
126 }
127
128 if (link(oldpath, newpath) == 0) {
129 #ifndef __FreeBSD__
130 return 0;
131 #endif
132 /* FreeBSD at least up to v6.2 converts EEXIST errors to
133 success. */
134 } else if (errno != EEXIST)
135 return -1;
136
137 /* We don't know if it succeeded or failed. stat() to make sure. */
138 if (stat(oldpath, &st) < 0)
139 return -1;
140 if (st.st_nlink == orig_link_count) {
141 errno = EEXIST;
142 return -1;
143 }
144 return 0;
145 }
146
nfs_flush_chown_uid(const char * path)147 static void nfs_flush_chown_uid(const char *path)
148 {
149
150 #ifdef ATTRCACHE_FLUSH_CHOWN_UID_1
151 uid_t uid = (uid_t)-1;
152 if (chown(path, uid, (gid_t)-1) < 0) {
153 if (errno == ESTALE || errno == EPERM || errno == ENOENT) {
154 /* attr cache is flushed */
155 return;
156 }
157 if (likely(errno == ENOENT)) {
158 nfs_flush_file_handle_cache_parent_dir(path);
159 return;
160 }
161 i_error("nfs_flush_chown_uid: chown(%s) failed: %m", path);
162 }
163 #else
164 struct stat st;
165
166 if (stat(path, &st) == 0) {
167 /* do nothing */
168 } else {
169 if (errno == ESTALE) {
170 /* ESTALE causes the OS to flush the attr cache */
171 return;
172 }
173 if (likely(errno == ENOENT)) {
174 nfs_flush_file_handle_cache_parent_dir(path);
175 return;
176 }
177 i_error("nfs_flush_chown_uid: stat(%s) failed: %m", path);
178 return;
179 }
180 /* we use chmod for this operation since chown has been seen to drop S_UID
181 and S_GID bits from directory inodes in certain conditions */
182 if (chmod(path, st.st_mode & 07777) < 0) {
183 if (errno == EPERM) {
184 /* attr cache is flushed */
185 return;
186 }
187 if (likely(errno == ENOENT)) {
188 nfs_flush_file_handle_cache_parent_dir(path);
189 return;
190 }
191 i_error("nfs_flush_chown_uid: chmod(%s, %04o) failed: %m",
192 path, st.st_mode & 07777);
193 }
194 #endif
195 }
196
197 #ifdef __FreeBSD__
nfs_flush_fchown_uid(const char * path,int fd)198 static bool nfs_flush_fchown_uid(const char *path, int fd)
199 {
200 uid_t uid;
201 #ifndef ATTRCACHE_FLUSH_CHOWN_UID_1
202 struct stat st;
203
204 if (fstat(fd, &st) < 0) {
205 if (likely(errno == ESTALE))
206 return FALSE;
207 i_error("nfs_flush_attr_cache_fchown: fstat(%s) failed: %m",
208 path);
209 return TRUE;
210 }
211 uid = st.st_uid;
212 #else
213 uid = (uid_t)-1;
214 #endif
215 if (fchown(fd, uid, (gid_t)-1) < 0) {
216 if (errno == ESTALE)
217 return FALSE;
218 if (likely(errno == EACCES || errno == EPERM)) {
219 /* attr cache is flushed */
220 return TRUE;
221 }
222
223 i_error("nfs_flush_attr_cache_fd_locked: fchown(%s) failed: %m",
224 path);
225 }
226 return TRUE;
227 }
228 #endif
229
230 #ifdef READ_CACHE_FLUSH_FCNTL
nfs_flush_fcntl(const char * path,int fd)231 static bool nfs_flush_fcntl(const char *path, int fd)
232 {
233 static bool locks_disabled = FALSE;
234 struct flock fl;
235 int ret;
236
237 if (locks_disabled)
238 return FALSE;
239
240 /* If the file was already locked, we'll just get the same lock
241 again. It should succeed just fine. If was was unlocked, we'll
242 have to get a lock and then unlock it. Linux 2.6 flushes read cache
243 only when read/write locking succeeded. */
244 fl.l_type = F_RDLCK;
245 fl.l_whence = SEEK_SET;
246 fl.l_start = 0;
247 fl.l_len = 0;
248
249 alarm(60);
250 ret = fcntl(fd, F_SETLKW, &fl);
251 alarm(0);
252
253 if (unlikely(ret < 0)) {
254 if (errno == ENOLCK) {
255 locks_disabled = TRUE;
256 return FALSE;
257 }
258 i_error("nfs_flush_fcntl: fcntl(%s, F_RDLCK) failed: %m", path);
259 return FALSE;
260 }
261
262 fl.l_type = F_UNLCK;
263 (void)fcntl(fd, F_SETLKW, &fl);
264 return TRUE;
265 }
266 #endif
267
nfs_flush_attr_cache_unlocked(const char * path)268 void nfs_flush_attr_cache_unlocked(const char *path)
269 {
270 int fd;
271
272 /* Try to flush the attribute cache the nice way first. */
273 fd = open(path, O_RDONLY);
274 if (fd != -1)
275 i_close_fd(&fd);
276 else if (errno == ESTALE) {
277 /* this already flushed the cache */
278 } else {
279 /* most likely ENOENT, which means a negative cache hit.
280 flush the file handles for its parent directory. */
281 nfs_flush_file_handle_cache_parent_dir(path);
282 }
283 }
284
nfs_flush_attr_cache_maybe_locked(const char * path)285 void nfs_flush_attr_cache_maybe_locked(const char *path)
286 {
287 nfs_flush_chown_uid(path);
288 }
289
nfs_flush_attr_cache_fd_locked(const char * path ATTR_UNUSED,int fd ATTR_UNUSED)290 void nfs_flush_attr_cache_fd_locked(const char *path ATTR_UNUSED,
291 int fd ATTR_UNUSED)
292 {
293 #ifdef __FreeBSD__
294 /* FreeBSD doesn't flush attribute cache with fcntl(), so we have
295 to do it ourself. */
296 (void)nfs_flush_fchown_uid(path, fd);
297 #else
298 /* Linux and Solaris are fine. */
299 #endif
300 }
301
302 static bool
nfs_flush_file_handle_cache_dir(const char * path,bool try_parent ATTR_UNUSED)303 nfs_flush_file_handle_cache_dir(const char *path, bool try_parent ATTR_UNUSED)
304 {
305 #ifdef __linux__
306 /* chown()ing parent is the safest way to handle this */
307 nfs_flush_chown_uid(path);
308 #else
309 /* rmdir() is the only choice with FreeBSD and Solaris */
310 if (unlikely(rmdir(path) == 0)) {
311 if (mkdir(path, 0700) == 0) {
312 i_warning("nfs_flush_file_handle_cache_dir: "
313 "rmdir(%s) unexpectedly "
314 "removed the dir. recreated.", path);
315 } else {
316 i_warning("nfs_flush_file_handle_cache_dir: "
317 "rmdir(%s) unexpectedly "
318 "removed the dir. mkdir() failed: %m", path);
319 }
320 } else if (errno == ESTALE || errno == ENOTDIR ||
321 errno == ENOTEMPTY || errno == EEXIST || errno == EACCES) {
322 /* expected failures */
323 } else if (errno == ENOENT) {
324 return FALSE;
325 } else if (errno == EINVAL && try_parent) {
326 /* Solaris gives this if we're trying to rmdir() the current
327 directory. Work around this by temporarily changing the
328 current directory to the parent directory. */
329 const char *cur_path, *p;
330 int cur_dir_fd;
331 bool ret;
332
333 cur_dir_fd = open(".", O_RDONLY);
334 if (cur_dir_fd == -1) {
335 i_error("open(.) failed for: %m");
336 return TRUE;
337 }
338
339 const char *error;
340 if (t_get_working_dir(&cur_path, &error) < 0) {
341 i_error("nfs_flush_file_handle_cache_dir: %s", error);
342 i_close_fd(&cur_dir_fd);
343 return TRUE;
344 }
345 p = strrchr(cur_path, '/');
346 if (p == NULL)
347 cur_path = "/";
348 else
349 cur_path = t_strdup_until(cur_path, p);
350 if (chdir(cur_path) < 0) {
351 i_error("nfs_flush_file_handle_cache_dir: "
352 "chdir() failed");
353 }
354 ret = nfs_flush_file_handle_cache_dir(path, FALSE);
355 if (fchdir(cur_dir_fd) < 0)
356 i_error("fchdir() failed: %m");
357 i_close_fd(&cur_dir_fd);
358 return ret;
359 } else {
360 i_error("nfs_flush_file_handle_cache_dir: "
361 "rmdir(%s) failed: %m", path);
362 }
363 #endif
364 return TRUE;
365 }
366
nfs_flush_file_handle_cache_parent_dir(const char * path)367 static void nfs_flush_file_handle_cache_parent_dir(const char *path)
368 {
369 const char *p;
370
371 p = strrchr(path, '/');
372 T_BEGIN {
373 if (p == NULL)
374 (void)nfs_flush_file_handle_cache_dir(".", TRUE);
375 else
376 (void)nfs_flush_file_handle_cache_dir(t_strdup_until(path, p),
377 TRUE);
378 } T_END;
379 }
380
nfs_flush_file_handle_cache(const char * path)381 void nfs_flush_file_handle_cache(const char *path)
382 {
383 nfs_flush_file_handle_cache_parent_dir(path);
384 }
385
nfs_flush_read_cache_locked(const char * path ATTR_UNUSED,int fd ATTR_UNUSED)386 void nfs_flush_read_cache_locked(const char *path ATTR_UNUSED,
387 int fd ATTR_UNUSED)
388 {
389 #ifdef READ_CACHE_FLUSH_FCNTL
390 /* already flushed when fcntl() was called */
391 #else
392 /* we can only hope that underlying filesystem uses micro/nanosecond
393 resolution so that attribute cache flushing notices mtime changes */
394 nfs_flush_attr_cache_fd_locked(path, fd);
395 #endif
396 }
397
nfs_flush_read_cache_unlocked(const char * path,int fd)398 void nfs_flush_read_cache_unlocked(const char *path, int fd)
399 {
400 #ifdef READ_CACHE_FLUSH_FCNTL
401 if (!nfs_flush_fcntl(path, fd))
402 nfs_flush_attr_cache_fd_locked(path, fd);
403 #else
404 nfs_flush_read_cache_locked(path, fd);
405 #endif
406 }
407