diff options
author | Alban Crequy <alban@endocode.com> | 2015-03-10 18:15:52 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2015-03-10 18:23:46 +0100 |
commit | f85ef957e647c5182acf5e64298f68e4b7fbfe8f (patch) | |
tree | 505151f058adc41e72d262d044cc2b0092a148aa /src/shared/util.c | |
parent | 27cc6f166bdebc0e698fb692993b801db2618866 (diff) |
util: add rename_noreplace
renameat2() exists since Linux 3.15 but btrfs support for the flag
RENAME_NOREPLACE was added later.
This patch implements a fallback when renameat2() returns EINVAL.
EINVAL is the error returned when the filesystem does not support one of
the flags.
Diffstat (limited to 'src/shared/util.c')
-rw-r--r-- | src/shared/util.c | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/src/shared/util.c b/src/shared/util.c index b90e10ffb2..55de83023d 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -8122,3 +8122,44 @@ void cmsg_close_all(struct msghdr *mh) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); } + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + struct stat buf; + int ret; + + ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret >= 0) + return 0; + + /* Even though renameat2() exists since Linux 3.15, btrfs added + * support for it later. If it is not implemented, fallback to another + * method. */ + if (errno != EINVAL) + return -errno; + + /* The link()/unlink() fallback does not work on directories. But + * renameat() without RENAME_NOREPLACE gives the same semantics on + * directories, except when newpath is an *empty* directory. This is + * good enough. */ + ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); + if (ret >= 0 && S_ISDIR(buf.st_mode)) { + ret = renameat(olddirfd, oldpath, newdirfd, newpath); + return ret >= 0 ? 0 : -errno; + } + + /* If it is not a directory, use the link()/unlink() fallback. */ + ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); + if (ret < 0) + return -errno; + + ret = unlinkat(olddirfd, oldpath, 0); + if (ret < 0) { + /* backup errno before the following unlinkat() alters it */ + ret = errno; + (void) unlinkat(newdirfd, newpath, 0); + errno = ret; + return -errno; + } + + return 0; +} |