8ae002
commit 85f7554cd97e7f03d8dc66278653045ef63a2221
8ae002
Author: Florian Weimer <fweimer@redhat.com>
8ae002
Date:   Wed Sep 21 15:24:01 2016 +0200
8ae002
8ae002
    Add test case for O_TMPFILE handling in open, openat
8ae002
    
8ae002
    Also put xasprintf into test-skeleton.c (written in such a way that
8ae002
    including <stdarg.h> is not needed).
8ae002
8ae002
commit 51364ff23e9760777bfea4eb9ac89c29a794074b
8ae002
Author: Florian Weimer <fweimer@redhat.com>
8ae002
Date:   Fri Sep 23 09:41:35 2016 +0200
8ae002
8ae002
    test-skeleton.c: Remove unintended #include <stdarg.h>.
8ae002
8ae002
Index: b/io/tst-open-tmpfile.c
8ae002
===================================================================
8ae002
--- /dev/null
8ae002
+++ b/io/tst-open-tmpfile.c
8ae002
@@ -0,0 +1,319 @@
8ae002
+/* Test open and openat with O_TMPFILE.
8ae002
+   Copyright (C) 2016 Free Software Foundation, Inc.
8ae002
+   This file is part of the GNU C Library.
8ae002
+
8ae002
+   The GNU C Library is free software; you can redistribute it and/or
8ae002
+   modify it under the terms of the GNU Lesser General Public
8ae002
+   License as published by the Free Software Foundation; either
8ae002
+   version 2.1 of the License, or (at your option) any later version.
8ae002
+
8ae002
+   The GNU C Library is distributed in the hope that it will be useful,
8ae002
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
8ae002
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
8ae002
+   Lesser General Public License for more details.
8ae002
+
8ae002
+   You should have received a copy of the GNU Lesser General Public
8ae002
+   License along with the GNU C Library; if not, see
8ae002
+   <http://www.gnu.org/licenses/>.  */
8ae002
+
8ae002
+/* This test verifies that open and openat work as expected, i.e. they
8ae002
+   create a deleted file with the requested file mode.  */
8ae002
+
8ae002
+#include <errno.h>
8ae002
+#include <fcntl.h>
8ae002
+#include <stdbool.h>
8ae002
+#include <stdio.h>
8ae002
+#include <stdlib.h>
8ae002
+#include <string.h>
8ae002
+#include <sys/stat.h>
8ae002
+#include <unistd.h>
8ae002
+
8ae002
+static int do_test (void);
8ae002
+
8ae002
+#define TEST_FUNCTION do_test ()
8ae002
+#include "../test-skeleton.c"
8ae002
+
8ae002
+#ifdef O_TMPFILE
8ae002
+typedef int (*wrapper_func) (const char *, int, mode_t);
8ae002
+
8ae002
+/* Error-checking wrapper for the open function, compatible with the
8ae002
+   wrapper_func type.  */
8ae002
+static int
8ae002
+wrap_open (const char *path, int flags, mode_t mode)
8ae002
+{
8ae002
+  int ret = open (path, flags, mode);
8ae002
+  if (ret < 0)
8ae002
+    {
8ae002
+      printf ("error: open (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  return ret;
8ae002
+}
8ae002
+
8ae002
+/* Error-checking wrapper for the openat function, compatible with the
8ae002
+   wrapper_func type.  */
8ae002
+static int
8ae002
+wrap_openat (const char *path, int flags, mode_t mode)
8ae002
+{
8ae002
+  int ret = openat (AT_FDCWD, path, flags, mode);
8ae002
+  if (ret < 0)
8ae002
+    {
8ae002
+      printf ("error: openat (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  return ret;
8ae002
+}
8ae002
+
8ae002
+/* Error-checking wrapper for the open64 function, compatible with the
8ae002
+   wrapper_func type.  */
8ae002
+static int
8ae002
+wrap_open64 (const char *path, int flags, mode_t mode)
8ae002
+{
8ae002
+  int ret = open64 (path, flags, mode);
8ae002
+  if (ret < 0)
8ae002
+    {
8ae002
+      printf ("error: open64 (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  return ret;
8ae002
+}
8ae002
+
8ae002
+/* Error-checking wrapper for the openat64 function, compatible with the
8ae002
+   wrapper_func type.  */
8ae002
+static int
8ae002
+wrap_openat64 (const char *path, int flags, mode_t mode)
8ae002
+{
8ae002
+  int ret = openat64 (AT_FDCWD, path, flags, mode);
8ae002
+  if (ret < 0)
8ae002
+    {
8ae002
+      printf ("error: openat64 (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  return ret;
8ae002
+}
8ae002
+
8ae002
+/* Return true if FD is flagged as deleted in /proc/self/fd, false if
8ae002
+   not.  */
8ae002
+static bool
8ae002
+is_file_deteted (int fd)
8ae002
+{
8ae002
+  char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd);
8ae002
+  char file_path[4096];
8ae002
+  ssize_t file_path_length
8ae002
+    = readlink (proc_fd_path, file_path, sizeof (file_path));
8ae002
+  if (file_path_length < 0)
8ae002
+    {
8ae002
+      printf ("error: readlink (\"%s\"): %m", proc_fd_path);
8ae002
+      free (proc_fd_path);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  free (proc_fd_path);
8ae002
+  if (file_path_length == sizeof (file_path))
8ae002
+    {
8ae002
+      printf ("error: path in /proc resolves to overlong file name: %.*s\n",
8ae002
+              (int) file_path_length, file_path);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  const char *deleted = " (deleted)";
8ae002
+  if (file_path_length < strlen (deleted))
8ae002
+    {
8ae002
+      printf ("error: path in /proc is too short: %.*s\n",
8ae002
+              (int) file_path_length, file_path);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  return memcmp (file_path + file_path_length - strlen (deleted),
8ae002
+              deleted, strlen (deleted)) == 0;
8ae002
+}
8ae002
+
8ae002
+/* Obtain a file name which is difficult to guess.  */
8ae002
+static char *
8ae002
+get_random_name (void)
8ae002
+{
8ae002
+  unsigned long long bytes[2];
8ae002
+  int random_device = open ("/dev/urandom", O_RDONLY);
8ae002
+  if (random_device < 0)
8ae002
+    {
8ae002
+      printf ("error: open (\"/dev/urandom\"): %m\n");
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  ssize_t ret = read (random_device, bytes, sizeof (bytes));
8ae002
+  if (ret < 0)
8ae002
+    {
8ae002
+      printf ("error: read (\"/dev/urandom\"): %m\n");
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  if (ret != sizeof (bytes))
8ae002
+    {
8ae002
+      printf ("error: short read from /dev/urandom: %zd\n", ret);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  close (random_device);
8ae002
+  return xasprintf ("tst-open-tmpfile-%08llx%08llx.tmp", bytes[0], bytes[1]);
8ae002
+}
8ae002
+
8ae002
+/* Check open/openat (as specified by OP and WRAPPER) with a specific
8ae002
+   PATH/FLAGS/MODE combination.  */
8ae002
+static void
8ae002
+check_wrapper_flags_mode (const char *op, wrapper_func wrapper,
8ae002
+                          const char *path, int flags, mode_t mode)
8ae002
+{
8ae002
+  int fd = wrapper (path, flags | O_TMPFILE, mode);
8ae002
+  struct stat64 st;
8ae002
+  if (fstat64 (fd, &st) != 0)
8ae002
+    {
8ae002
+      printf ("error: fstat64: %m\n");
8ae002
+      exit (1);
8ae002
+    }
8ae002
+
8ae002
+  /* Verify that the mode was correctly processed.  */
8ae002
+  int actual_mode = st.st_mode & 0777;
8ae002
+  if (actual_mode != mode)
8ae002
+    {
8ae002
+      printf ("error: unexpected mode; expected 0%03o, actual 0%03o\n",
8ae002
+              mode, actual_mode);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+
8ae002
+  /* Check that the file is marked as deleted in /proc.  */
8ae002
+  if (!is_file_deteted (fd))
8ae002
+    {
8ae002
+      printf ("error: path in /proc is not marked as deleted\n");
8ae002
+      exit (1);
8ae002
+    }
8ae002
+
8ae002
+  /* Check that the file can be turned into a regular file with
8ae002
+     linkat.  Open a file descriptor for the directory at PATH.  Use
8ae002
+     AT_FDCWD if PATH is ".", to exercise that functionality as
8ae002
+     well.  */
8ae002
+  int path_fd;
8ae002
+  if (strcmp (path, ".") == 0)
8ae002
+    path_fd = AT_FDCWD;
8ae002
+  else
8ae002
+    {
8ae002
+      path_fd = open (path, O_RDONLY | O_DIRECTORY);
8ae002
+      if (path_fd < 0)
8ae002
+        {
8ae002
+          printf ("error: open (\"%s\"): %m\n", path);
8ae002
+          exit (1);
8ae002
+        }
8ae002
+    }
8ae002
+
8ae002
+  /* Use a hard-to-guess name for the new directory entry.  */
8ae002
+  char *new_name = get_random_name ();
8ae002
+
8ae002
+  /* linkat does not require privileges if the path in /proc/self/fd
8ae002
+     is used.  */
8ae002
+  char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd);
8ae002
+  if (linkat (AT_FDCWD, proc_fd_path, path_fd, new_name,
8ae002
+              AT_SYMLINK_FOLLOW) == 0)
8ae002
+    {
8ae002
+      if (unlinkat (path_fd, new_name, 0) != 0 && errno != ENOENT)
8ae002
+        {
8ae002
+          printf ("error: unlinkat (\"%s/%s\"): %m\n", path, new_name);
8ae002
+          exit (1);
8ae002
+        }
8ae002
+    }
8ae002
+  else
8ae002
+    {
8ae002
+      /* linkat failed.  This is expected if O_EXCL was specified.  */
8ae002
+      if ((flags & O_EXCL) == 0)
8ae002
+        {
8ae002
+          printf ("error: linkat failed after %s (\"%s\", 0x%x, 0%03o): %m\n",
8ae002
+                  op, path, flags, mode);
8ae002
+          exit (1);
8ae002
+        }
8ae002
+    }
8ae002
+
8ae002
+  free (proc_fd_path);
8ae002
+  free (new_name);
8ae002
+  if (path_fd != AT_FDCWD)
8ae002
+    close (path_fd);
8ae002
+  close (fd);
8ae002
+}
8ae002
+
8ae002
+/* Check OP/WRAPPER with various flags at a specific PATH and
8ae002
+   MODE.  */
8ae002
+static void
8ae002
+check_wrapper_mode (const char *op, wrapper_func wrapper,
8ae002
+                    const char *path, mode_t mode)
8ae002
+{
8ae002
+  check_wrapper_flags_mode (op, wrapper, path, O_WRONLY, mode);
8ae002
+  check_wrapper_flags_mode (op, wrapper, path, O_WRONLY | O_EXCL, mode);
8ae002
+  check_wrapper_flags_mode (op, wrapper, path, O_RDWR, mode);
8ae002
+  check_wrapper_flags_mode (op, wrapper, path, O_RDWR | O_EXCL, mode);
8ae002
+}
8ae002
+
8ae002
+/* Check open/openat with varying permissions.  */
8ae002
+static void
8ae002
+check_wrapper (const char *op, wrapper_func wrapper,
8ae002
+                    const char *path)
8ae002
+{
8ae002
+  printf ("info: testing %s at: %s\n", op, path);
8ae002
+  check_wrapper_mode (op, wrapper, path, 0);
8ae002
+  check_wrapper_mode (op, wrapper, path, 0640);
8ae002
+  check_wrapper_mode (op, wrapper, path, 0600);
8ae002
+  check_wrapper_mode (op, wrapper, path, 0755);
8ae002
+  check_wrapper_mode (op, wrapper, path, 0750);
8ae002
+}
8ae002
+
8ae002
+/* Verify that the directory at PATH supports O_TMPFILE.  Exit with
8ae002
+   status 77 (unsupported) if the kernel does not support O_TMPFILE.
8ae002
+   Even with kernel support, not all file systems O_TMPFILE, so return
8ae002
+   true if the directory supports O_TMPFILE, false if not.  */
8ae002
+static bool
8ae002
+probe_path (const char *path)
8ae002
+{
8ae002
+  int fd = openat (AT_FDCWD, path, O_TMPFILE | O_RDWR, 0);
8ae002
+  if (fd < 0)
8ae002
+    {
8ae002
+      if (errno == EISDIR)
8ae002
+        /* The system does not support O_TMPFILE.  */
8ae002
+        {
8ae002
+          printf ("info: kernel does not support O_TMPFILE\n");
8ae002
+          exit (77);
8ae002
+        }
8ae002
+      if (errno == EOPNOTSUPP)
8ae002
+        {
8ae002
+          printf ("info: path does not support O_TMPFILE: %s\n", path);
8ae002
+          return false;
8ae002
+        }
8ae002
+      printf ("error: openat (\"%s\", O_TMPFILE | O_RDWR): %m\n", path);
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  close (fd);
8ae002
+  return true;
8ae002
+}
8ae002
+
8ae002
+static int
8ae002
+do_test (void)
8ae002
+{
8ae002
+  umask (0);
8ae002
+  const char *paths[] = { ".", "/dev/shm", "/tmp",
8ae002
+                          getenv ("TEST_TMPFILE_PATH"),
8ae002
+                          NULL };
8ae002
+  bool supported = false;
8ae002
+  for (int i = 0; paths[i] != NULL; ++i)
8ae002
+    if (probe_path (paths[i]))
8ae002
+      {
8ae002
+        supported = true;
8ae002
+        check_wrapper ("open", wrap_open, paths[i]);
8ae002
+        check_wrapper ("openat", wrap_openat, paths[i]);
8ae002
+        check_wrapper ("open64", wrap_open64, paths[i]);
8ae002
+        check_wrapper ("openat64", wrap_openat64, paths[i]);
8ae002
+      }
8ae002
+
8ae002
+  if (!supported)
8ae002
+    return 77;
8ae002
+
8ae002
+  return 0;
8ae002
+}
8ae002
+
8ae002
+#else  /* !O_TMPFILE */
8ae002
+
8ae002
+static int
8ae002
+do_test (void)
8ae002
+{
8ae002
+  return 77;
8ae002
+}
8ae002
+
8ae002
+#endif  /* O_TMPFILE */
8ae002
Index: b/test-skeleton.c
8ae002
===================================================================
8ae002
--- a/test-skeleton.c
8ae002
+++ b/test-skeleton.c
8ae002
@@ -32,6 +32,7 @@
8ae002
 #include <sys/wait.h>
8ae002
 #include <sys/param.h>
8ae002
 #include <time.h>
8ae002
+#include <stdarg.h>
8ae002
 
8ae002
 /* The test function is normally called `do_test' and it is called
8ae002
    with argc and argv as the arguments.  We nevertheless provide the
8ae002
@@ -63,6 +64,20 @@ static pid_t pid;
8ae002
 /* Directory to place temporary files in.  */
8ae002
 static const char *test_dir;
8ae002
 
8ae002
+/* Call asprintf with error checking.  */
8ae002
+__attribute__ ((always_inline, format (printf, 1, 2)))
8ae002
+static __inline__ char *
8ae002
+xasprintf (const char *format, ...)
8ae002
+{
8ae002
+  char *result;
8ae002
+  if (asprintf (&result, format, __builtin_va_arg_pack ()) < 0)
8ae002
+    {
8ae002
+      printf ("error: asprintf: %m\n");
8ae002
+      exit (1);
8ae002
+    }
8ae002
+  return result;
8ae002
+}
8ae002
+
8ae002
 /* List of temporary files.  */
8ae002
 struct temp_name_list
8ae002
 {
8ae002
Index: b/io/Makefile
8ae002
===================================================================
8ae002
--- a/io/Makefile
8ae002
+++ b/io/Makefile
8ae002
@@ -69,7 +69,8 @@ tests		:= test-utime test-stat test-stat
8ae002
 		   tst-renameat tst-fchownat tst-fchmodat tst-faccessat \
8ae002
 		   tst-symlinkat tst-linkat tst-readlinkat tst-mkdirat \
8ae002
 		   tst-mknodat tst-mkfifoat tst-ttyname_r bug-ftw5 \
8ae002
-		   tst-posix_fallocate
8ae002
+		   tst-posix_fallocate \
8ae002
+		   tst-open-tmpfile
8ae002
 
8ae002
 include ../Rules
8ae002