#include "lib.h"
#include "abspath.h"
#include "nfs-workarounds.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#if defined (__linux__) || defined(__sun)
# define READ_CACHE_FLUSH_FCNTL
#endif
#if defined(__FreeBSD__) || defined(__sun)
# define ATTRCACHE_FLUSH_CHOWN_UID_1
#endif
static void nfs_flush_file_handle_cache_parent_dir(const char *path);
static int
nfs_safe_do(const char *path, int (*callback)(const char *path, void *context),
void *context)
{
unsigned int i;
int ret;
for (i = 1;; i++) {
ret = callback(path, context);
#ifdef __APPLE__
if (ret == 0)
break;
else if (errno == ESTALE && i < NFS_ESTALE_RETRY_COUNT) {
nfs_flush_file_handle_cache(path);
} else if (errno == ENOENT && i < 3) {
} else
break;
#else
if (ret == 0 || errno != ESTALE || i == NFS_ESTALE_RETRY_COUNT)
break;
nfs_flush_file_handle_cache(path);
#endif
}
return ret;
}
struct nfs_safe_open_context {
int flags;
int fd;
};
static int nfs_safe_open_callback(const char *path, void *context)
{
struct nfs_safe_open_context *ctx = context;
ctx->fd = open(path, ctx->flags);
return ctx->fd == -1 ? -1 : 0;
}
int nfs_safe_open(const char *path, int flags)
{
struct nfs_safe_open_context ctx;
i_assert((flags & O_CREAT) == 0);
ctx.flags = flags;
if (nfs_safe_do(path, nfs_safe_open_callback, &ctx) < 0)
return -1;
return ctx.fd;
}
static int nfs_safe_stat_callback(const char *path, void *context)
{
struct stat *buf = context;
return stat(path, buf);
}
int nfs_safe_stat(const char *path, struct stat *buf)
{
return nfs_safe_do(path, nfs_safe_stat_callback, buf);
}
static int nfs_safe_lstat_callback(const char *path, void *context)
{
struct stat *buf = context;
return lstat(path, buf);
}
int nfs_safe_lstat(const char *path, struct stat *buf)
{
return nfs_safe_do(path, nfs_safe_lstat_callback, buf);
}
int nfs_safe_link(const char *oldpath, const char *newpath, bool links1)
{
struct stat st;
nlink_t orig_link_count = 1;
if (!links1) {
if (stat(oldpath, &st) < 0)
return -1;
orig_link_count = st.st_nlink;
}
if (link(oldpath, newpath) == 0) {
#ifndef __FreeBSD__
return 0;
#endif
} else if (errno != EEXIST)
return -1;
if (stat(oldpath, &st) < 0)
return -1;
if (st.st_nlink == orig_link_count) {
errno = EEXIST;
return -1;
}
return 0;
}
static void nfs_flush_chown_uid(const char *path)
{
uid_t uid;
#ifdef ATTRCACHE_FLUSH_CHOWN_UID_1
uid = (uid_t)-1;
#else
struct stat st;
if (stat(path, &st) == 0)
uid = st.st_uid;
else {
if (errno == ESTALE) {
return;
}
if (likely(errno == ENOENT)) {
nfs_flush_file_handle_cache_parent_dir(path);
return;
}
i_error("nfs_flush_chown_uid: stat(%s) failed: %m", path);
return;
}
#endif
if (chown(path, uid, (gid_t)-1) < 0) {
if (errno == ESTALE || errno == EPERM || errno == ENOENT) {
return;
}
if (likely(errno == ENOENT)) {
nfs_flush_file_handle_cache_parent_dir(path);
return;
}
i_error("nfs_flush_chown_uid: chown(%s) failed: %m", path);
}
}
#ifdef __FreeBSD__
static bool nfs_flush_fchown_uid(const char *path, int fd)
{
uid_t uid;
#ifndef ATTRCACHE_FLUSH_CHOWN_UID_1
struct stat st;
if (fstat(fd, &st) < 0) {
if (likely(errno == ESTALE))
return FALSE;
i_error("nfs_flush_attr_cache_fchown: fstat(%s) failed: %m",
path);
return TRUE;
}
uid = st.st_uid;
#else
uid = (uid_t)-1;
#endif
if (fchown(fd, uid, (gid_t)-1) < 0) {
if (errno == ESTALE)
return FALSE;
if (likely(errno == EACCES || errno == EPERM)) {
return TRUE;
}
i_error("nfs_flush_attr_cache_fd_locked: fchown(%s) failed: %m",
path);
}
return TRUE;
}
#endif
#ifdef READ_CACHE_FLUSH_FCNTL
static bool nfs_flush_fcntl(const char *path, int fd)
{
static bool locks_disabled = FALSE;
struct flock fl;
int ret;
if (locks_disabled)
return FALSE;
fl.l_type = F_RDLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
alarm(60);
ret = fcntl(fd, F_SETLKW, &fl);
alarm(0);
if (unlikely(ret < 0)) {
if (errno == ENOLCK) {
locks_disabled = TRUE;
return FALSE;
}
i_error("nfs_flush_fcntl: fcntl(%s, F_RDLCK) failed: %m", path);
return FALSE;
}
fl.l_type = F_UNLCK;
(void)fcntl(fd, F_SETLKW, &fl);
return TRUE;
}
#endif
void nfs_flush_attr_cache_unlocked(const char *path)
{
int fd;
fd = open(path, O_RDONLY);
if (fd != -1)
(void)close(fd);
else if (errno == ESTALE) {
} else {
nfs_flush_file_handle_cache_parent_dir(path);
}
}
void nfs_flush_attr_cache_maybe_locked(const char *path)
{
nfs_flush_chown_uid(path);
}
bool nfs_flush_attr_cache_fd_locked(const char *path ATTR_UNUSED,
int fd ATTR_UNUSED)
{
#ifdef __FreeBSD__
return nfs_flush_fchown_uid(path, fd);
#else
return TRUE;
#endif
}
static bool
nfs_flush_file_handle_cache_dir(const char *path, bool try_parent ATTR_UNUSED)
{
#ifdef __linux__
nfs_flush_chown_uid(path);
#else
if (unlikely(rmdir(path) == 0)) {
if (mkdir(path, 0700) == 0) {
i_warning("nfs_flush_file_handle_cache_dir: "
"rmdir(%s) unexpectedly "
"removed the dir. recreated.", path);
} else {
i_warning("nfs_flush_file_handle_cache_dir: "
"rmdir(%s) unexpectedly "
"removed the dir. mkdir() failed: %m", path);
}
} else if (errno == ESTALE || errno == ENOTDIR ||
errno == ENOTEMPTY || errno == EEXIST || errno == EACCES) {
} else if (errno == ENOENT) {
return FALSE;
} else if (errno == EINVAL && try_parent) {
const char *cur_path, *p;
int cur_dir_fd;
bool ret;
cur_dir_fd = open(".", O_RDONLY);
if (cur_dir_fd == -1) {
i_error("open(.) failed for: %m");
return TRUE;
}
if (t_get_current_dir(&cur_path) < 0) {
i_error("nfs_flush_file_handle_cache_dir: "
"getcwd() failed");
(void)close(cur_dir_fd);
return TRUE;
}
p = strrchr(cur_path, '/');
if (p == NULL)
cur_path = "/";
else
cur_path = t_strdup_until(cur_path, p);
if (chdir(cur_path) < 0) {
i_error("nfs_flush_file_handle_cache_dir: "
"chdir() failed");
}
ret = nfs_flush_file_handle_cache_dir(path, FALSE);
if (fchdir(cur_dir_fd) < 0)
i_error("fchdir() failed: %m");
(void)close(cur_dir_fd);
return ret;
} else {
i_error("nfs_flush_file_handle_cache_dir: "
"rmdir(%s) failed: %m", path);
}
#endif
return TRUE;
}
static void nfs_flush_file_handle_cache_parent_dir(const char *path)
{
const char *p;
p = strrchr(path, '/');
T_BEGIN {
if (p == NULL)
nfs_flush_file_handle_cache_dir(".", TRUE);
else
nfs_flush_file_handle_cache_dir(t_strdup_until(path, p),
TRUE);
} T_END;
}
void nfs_flush_file_handle_cache(const char *path)
{
nfs_flush_file_handle_cache_parent_dir(path);
}
void nfs_flush_read_cache_locked(const char *path ATTR_UNUSED,
int fd ATTR_UNUSED)
{
#ifdef READ_CACHE_FLUSH_FCNTL
#else
nfs_flush_attr_cache_fd_locked(path, fd);
#endif
}
void nfs_flush_read_cache_unlocked(const char *path, int fd)
{
#ifdef READ_CACHE_FLUSH_FCNTL
if (!nfs_flush_fcntl(path, fd))
nfs_flush_attr_cache_fd_locked(path, fd);
#else
nfs_flush_read_cache_locked(path, fd);
#endif
}