Make do_readlink() support fake-super w/o O_NOFOLLOW.
authorWayne Davison <wayned@samba.org>
Mon, 19 Sep 2011 16:18:18 +0000 (09:18 -0700)
committerWayne Davison <wayned@samba.org>
Mon, 19 Sep 2011 16:29:00 +0000 (09:29 -0700)
syscall.c

index c6c571a6adce78ecfa1db5e3f2eea8ba7c657331..6d51d3cb6f5bba972dba15db7db24b53588484ab 100644 (file)
--- a/syscall.c
+++ b/syscall.c
@@ -87,7 +87,7 @@ ssize_t do_readlink(const char *path, char *buf, size_t bufsiz)
 {
        /* For --fake-super, we read the link from the file. */
        if (am_root < 0) {
 {
        /* For --fake-super, we read the link from the file. */
        if (am_root < 0) {
-               int fd = open(path, O_RDONLY|O_NOFOLLOW);
+               int fd = do_open_nofollow(path, O_RDONLY);
                if (fd >= 0) {
                        int len = read(fd, buf, bufsiz);
                        close(fd);
                if (fd >= 0) {
                        int len = read(fd, buf, bufsiz);
                        close(fd);
@@ -443,3 +443,50 @@ int do_fallocate(int fd, OFF_T offset, OFF_T length)
 #endif
 }
 #endif
 #endif
 }
 #endif
+
+int do_open_nofollow(const char *pathname, int flags)
+{
+#ifndef O_NOFOLLOW
+       struct stat f_st, l_st;
+#endif
+       int fd;
+
+       if (flags != O_RDONLY) {
+               RETURN_ERROR_IF(dry_run, 0);
+               RETURN_ERROR_IF_RO_OR_LO;
+#ifndef O_NOFOLLOW
+               /* This function doesn't support write attempts w/o O_NOFOLLOW. */
+               errno = EINVAL;
+               return -1;
+#endif
+       }
+
+#ifdef O_NOFOLLOW
+       fd = open(pathname, flags|O_NOFOLLOW);
+#else
+       if ((fd = open(pathname, flags)) < 0)
+               return fd;
+
+       if (do_fstat(fd, &f_st) < 0) {
+         close_and_return_error:
+               {
+                       int save_errno = errno;
+                       close(fd);
+                       errno = save_errno;
+               }
+               return -1;
+       }
+       if (do_lstat(pathname, &l_st) < 0)
+               goto close_and_return_error;
+       if (S_ISLNK(l_st.st_mode)) {
+               errno = ELOOP;
+               goto close_and_return_error;
+       }
+       if (l_st.st_dev != f_st.st_dev || l_st.st_ino != f_st.st_ino) {
+               errno = EINVAL;
+               goto close_and_return_error;
+       }
+#endif
+
+       return fd;
+}