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