From 1963fa800f4554d729f14f7f3a01a5b4af4dfbc1 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Thu, 14 Mar 2019 11:14:38 -0500 Subject: renameat: further bring towards standard behaviour --- src/unistd/renameat.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/unistd/renameat.c b/src/unistd/renameat.c index 07386407..d0f31c21 100644 --- a/src/unistd/renameat.c +++ b/src/unistd/renameat.c @@ -1,16 +1,23 @@ #include +#include #include #include #include #include +#include +#include +#include #include "syscall.h" int renameat(int oldfd, const char *old, int newfd, const char *new) { char old_copy[PATH_MAX+1], new_copy[PATH_MAX+1]; char *base; + size_t old_size, new_size; + struct stat statbuf; - if (strlen(old) > PATH_MAX || strlen(new) > PATH_MAX) { + if ((old_size = strlen(old)) > PATH_MAX || \ + (new_size = strlen(new)) > PATH_MAX) { errno = ENAMETOOLONG; return -1; } @@ -34,5 +41,31 @@ int renameat(int oldfd, const char *old, int newfd, const char *new) return -1; } + /* The Linux kernel will fail when attempting to rename a symlink of a + directory with a trailing slash. We therefore have to handle this + case ourselves. */ + if (old[old_size - 1] == '/') { + /* Calling stat(2) on a symlink to a dir with the trailing + slash causes stat(2) to return the actual directory instead + of the symlink itself. */ + strcpy(old_copy, old); + old_copy[old_size - 1] = '\0'; + if (fstatat(oldfd, old_copy, &statbuf, AT_SYMLINK_NOFOLLOW) == -1) { + return -1; + } + if (S_ISLNK(statbuf.st_mode)) { + if (fstatat(oldfd, old, &statbuf, 0) == -1) { + return -1; + } + if (S_ISDIR(statbuf.st_mode)) { + old = old_copy; + } else { + /* may as well not waste the syscall */ + errno = ENOTDIR; + return -1; + } + } + } + return syscall(SYS_renameat, oldfd, old, newfd, new); } -- cgit v1.2.3-60-g2f50