From f85ef957e647c5182acf5e64298f68e4b7fbfe8f Mon Sep 17 00:00:00 2001 From: Alban Crequy Date: Tue, 10 Mar 2015 18:15:52 +0100 Subject: [PATCH 1/1] 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. --- src/import/import-raw.c | 5 +++-- src/import/import-tar.c | 5 +++-- src/import/pull-raw.c | 4 ++-- src/import/pull-tar.c | 5 +++-- src/shared/copy.c | 12 ++++++++--- src/shared/machine-image.c | 5 +++-- src/shared/machine-pool.c | 5 +++-- src/shared/util.c | 41 ++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 2 ++ 9 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 25b52f7cb..7d1ac2afd 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -245,8 +245,9 @@ static int raw_import_finish(RawImport *i) { (void) rm_rf_dangerous(i->final_path, false, true, false); } - if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0) - return log_error_errno(errno, "Failed to move image into place: %m"); + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) + return log_error_errno(r, "Failed to move image into place: %m"); free(i->temp_path); i->temp_path = NULL; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index dd9557566..ef2345c7b 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -201,8 +201,9 @@ static int tar_import_finish(TarImport *i) { (void) rm_rf_dangerous(i->final_path, false, true, false); } - if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0) - return log_error_errno(errno, "Failed to move image into place: %m"); + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) + return log_error_errno(r, "Failed to move image into place: %m"); free(i->temp_path); i->temp_path = NULL; diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index d1d77d598..c0c6d57ea 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -384,9 +384,9 @@ static void raw_pull_job_on_finished(PullJob *j) { if (r < 0) goto finish; - r = renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE); + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); if (r < 0) { - r = log_error_errno(errno, "Failed to move RAW file into place: %m"); + log_error_errno(r, "Failed to move RAW file into place: %m"); goto finish; } diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 16994e1c2..58cafdd78 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -281,8 +281,9 @@ static void tar_pull_job_on_finished(PullJob *j) { if (r < 0) goto finish; - if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0) { - r = log_error_errno(errno, "Failed to rename to final image name: %m"); + r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + if (r < 0) { + log_error_errno(r, "Failed to rename to final image name: %m"); goto finish; } diff --git a/src/shared/copy.c b/src/shared/copy.c index 0239a5806..f9ec6733b 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -404,9 +404,15 @@ int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace if (r < 0) return r; - if (renameat2(AT_FDCWD, t, AT_FDCWD, to, replace ? 0 : RENAME_NOREPLACE) < 0) { - unlink_noerrno(t); - return -errno; + if (replace) { + r = renameat(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) + r = -errno; + } else + r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) { + (void) unlink_noerrno(t); + return r; } return 0; diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index 552847e0f..00337e7c9 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -440,8 +440,9 @@ int image_rename(Image *i, const char *new_name) { if (!nn) return -ENOMEM; - if (renameat2(AT_FDCWD, i->path, AT_FDCWD, new_path, RENAME_NOREPLACE) < 0) - return -errno; + r = rename_noreplace(AT_FDCWD, i->path, AT_FDCWD, new_path); + if (r < 0) + return r; /* Restore the immutable bit, if it was set before */ if (file_attr & FS_IMMUTABLE_FL) diff --git a/src/shared/machine-pool.c b/src/shared/machine-pool.c index e7671a3c5..0fae62394 100644 --- a/src/shared/machine-pool.c +++ b/src/shared/machine-pool.c @@ -140,8 +140,9 @@ static int setup_machine_raw(uint64_t size, sd_bus_error *error) { goto fail; } - if (renameat2(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw", RENAME_NOREPLACE) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to move /var/lib/machines.raw into place: %m"); + r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw"); + if (r < 0) { + sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m"); goto fail; } diff --git a/src/shared/util.c b/src/shared/util.c index b90e10ffb..55de83023 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; +} diff --git a/src/shared/util.h b/src/shared/util.h index d2da3e289..d229e1e68 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -1081,3 +1081,5 @@ void sigkill_wait(pid_t *pid); int syslog_parse_priority(const char **p, int *priority, bool with_facility); void cmsg_close_all(struct msghdr *mh); + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); -- 2.30.2