(root)/
glibc-2.38/
io/
tst-open-tmpfile.c
       1  /* Test open and openat with O_TMPFILE.
       2     Copyright (C) 2016-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  /* This test verifies that open and openat work as expected, i.e. they
      20     create a deleted file with the requested file mode.  */
      21  
      22  #include <errno.h>
      23  #include <fcntl.h>
      24  #include <stdbool.h>
      25  #include <stdio.h>
      26  #include <stdlib.h>
      27  #include <string.h>
      28  #include <sys/stat.h>
      29  #include <unistd.h>
      30  
      31  #include <support/support.h>
      32  
      33  #ifdef O_TMPFILE
      34  typedef int (*wrapper_func) (const char *, int, mode_t);
      35  
      36  /* Error-checking wrapper for the open function, compatible with the
      37     wrapper_func type.  */
      38  static int
      39  wrap_open (const char *path, int flags, mode_t mode)
      40  {
      41    int ret = open (path, flags, mode);
      42    if (ret < 0)
      43      {
      44        printf ("error: open (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
      45        exit (1);
      46      }
      47    return ret;
      48  }
      49  
      50  /* Error-checking wrapper for the openat function, compatible with the
      51     wrapper_func type.  */
      52  static int
      53  wrap_openat (const char *path, int flags, mode_t mode)
      54  {
      55    int ret = openat (AT_FDCWD, path, flags, mode);
      56    if (ret < 0)
      57      {
      58        printf ("error: openat (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
      59        exit (1);
      60      }
      61    return ret;
      62  }
      63  
      64  /* Error-checking wrapper for the open64 function, compatible with the
      65     wrapper_func type.  */
      66  static int
      67  wrap_open64 (const char *path, int flags, mode_t mode)
      68  {
      69    int ret = open64 (path, flags, mode);
      70    if (ret < 0)
      71      {
      72        printf ("error: open64 (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
      73        exit (1);
      74      }
      75    return ret;
      76  }
      77  
      78  /* Error-checking wrapper for the openat64 function, compatible with the
      79     wrapper_func type.  */
      80  static int
      81  wrap_openat64 (const char *path, int flags, mode_t mode)
      82  {
      83    int ret = openat64 (AT_FDCWD, path, flags, mode);
      84    if (ret < 0)
      85      {
      86        printf ("error: openat64 (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode);
      87        exit (1);
      88      }
      89    return ret;
      90  }
      91  
      92  /* Return true if FD is flagged as deleted in /proc/self/fd, false if
      93     not.  */
      94  static bool
      95  is_file_deleted (int fd)
      96  {
      97    char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd);
      98    char file_path[4096];
      99    ssize_t file_path_length
     100      = readlink (proc_fd_path, file_path, sizeof (file_path));
     101    if (file_path_length < 0)
     102      {
     103        printf ("error: readlink (\"%s\"): %m", proc_fd_path);
     104        free (proc_fd_path);
     105        exit (1);
     106      }
     107    free (proc_fd_path);
     108    if (file_path_length == sizeof (file_path))
     109      {
     110        printf ("error: path in /proc resolves to overlong file name: %.*s\n",
     111                (int) file_path_length, file_path);
     112        exit (1);
     113      }
     114    const char *deleted = " (deleted)";
     115    if (file_path_length < strlen (deleted))
     116      {
     117        printf ("error: path in /proc is too short: %.*s\n",
     118                (int) file_path_length, file_path);
     119        exit (1);
     120      }
     121    return memcmp (file_path + file_path_length - strlen (deleted),
     122                deleted, strlen (deleted)) == 0;
     123  }
     124  
     125  /* Obtain a file name which is difficult to guess.  */
     126  static char *
     127  get_random_name (void)
     128  {
     129    unsigned long long bytes[2];
     130    int random_device = open ("/dev/urandom", O_RDONLY);
     131    if (random_device < 0)
     132      {
     133        printf ("error: open (\"/dev/urandom\"): %m\n");
     134        exit (1);
     135      }
     136    ssize_t ret = read (random_device, bytes, sizeof (bytes));
     137    if (ret < 0)
     138      {
     139        printf ("error: read (\"/dev/urandom\"): %m\n");
     140        exit (1);
     141      }
     142    if (ret != sizeof (bytes))
     143      {
     144        printf ("error: short read from /dev/urandom: %zd\n", ret);
     145        exit (1);
     146      }
     147    close (random_device);
     148    return xasprintf ("tst-open-tmpfile-%08llx%08llx.tmp", bytes[0], bytes[1]);
     149  }
     150  
     151  /* Check open/openat (as specified by OP and WRAPPER) with a specific
     152     PATH/FLAGS/MODE combination.  */
     153  static void
     154  check_wrapper_flags_mode (const char *op, wrapper_func wrapper,
     155                            const char *path, int flags, mode_t mode)
     156  {
     157    int fd = wrapper (path, flags | O_TMPFILE, mode);
     158    struct stat64 st;
     159    if (fstat64 (fd, &st) != 0)
     160      {
     161        printf ("error: fstat64: %m\n");
     162        exit (1);
     163      }
     164  
     165    /* Verify that the mode was correctly processed.  */
     166    int actual_mode = st.st_mode & 0777;
     167    if (actual_mode != mode)
     168      {
     169        printf ("error: unexpected mode; expected 0%03o, actual 0%03o\n",
     170                mode, actual_mode);
     171        exit (1);
     172      }
     173  
     174    /* Check that the file is marked as deleted in /proc.  */
     175    if (!is_file_deleted (fd))
     176      {
     177        printf ("error: path in /proc is not marked as deleted\n");
     178        exit (1);
     179      }
     180  
     181    /* Check that the file can be turned into a regular file with
     182       linkat.  Open a file descriptor for the directory at PATH.  Use
     183       AT_FDCWD if PATH is ".", to exercise that functionality as
     184       well.  */
     185    int path_fd;
     186    if (strcmp (path, ".") == 0)
     187      path_fd = AT_FDCWD;
     188    else
     189      {
     190        path_fd = open (path, O_RDONLY | O_DIRECTORY);
     191        if (path_fd < 0)
     192          {
     193            printf ("error: open (\"%s\"): %m\n", path);
     194            exit (1);
     195          }
     196      }
     197  
     198    /* Use a hard-to-guess name for the new directory entry.  */
     199    char *new_name = get_random_name ();
     200  
     201    /* linkat does not require privileges if the path in /proc/self/fd
     202       is used.  */
     203    char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd);
     204    if (linkat (AT_FDCWD, proc_fd_path, path_fd, new_name,
     205                AT_SYMLINK_FOLLOW) == 0)
     206      {
     207        if (unlinkat (path_fd, new_name, 0) != 0 && errno != ENOENT)
     208          {
     209            printf ("error: unlinkat (\"%s/%s\"): %m\n", path, new_name);
     210            exit (1);
     211          }
     212      }
     213    else
     214      {
     215        /* linkat failed.  This is expected if O_EXCL was specified.  */
     216        if ((flags & O_EXCL) == 0)
     217          {
     218            printf ("error: linkat failed after %s (\"%s\", 0x%x, 0%03o): %m\n",
     219                    op, path, flags, mode);
     220            exit (1);
     221          }
     222      }
     223  
     224    free (proc_fd_path);
     225    free (new_name);
     226    if (path_fd != AT_FDCWD)
     227      close (path_fd);
     228    close (fd);
     229  }
     230  
     231  /* Check OP/WRAPPER with various flags at a specific PATH and
     232     MODE.  */
     233  static void
     234  check_wrapper_mode (const char *op, wrapper_func wrapper,
     235                      const char *path, mode_t mode)
     236  {
     237    check_wrapper_flags_mode (op, wrapper, path, O_WRONLY, mode);
     238    check_wrapper_flags_mode (op, wrapper, path, O_WRONLY | O_EXCL, mode);
     239    check_wrapper_flags_mode (op, wrapper, path, O_RDWR, mode);
     240    check_wrapper_flags_mode (op, wrapper, path, O_RDWR | O_EXCL, mode);
     241  }
     242  
     243  /* Check open/openat with varying permissions.  */
     244  static void
     245  check_wrapper (const char *op, wrapper_func wrapper,
     246                      const char *path)
     247  {
     248    printf ("info: testing %s at: %s\n", op, path);
     249    check_wrapper_mode (op, wrapper, path, 0);
     250    check_wrapper_mode (op, wrapper, path, 0640);
     251    check_wrapper_mode (op, wrapper, path, 0600);
     252    check_wrapper_mode (op, wrapper, path, 0755);
     253    check_wrapper_mode (op, wrapper, path, 0750);
     254  }
     255  
     256  /* Verify that the directory at PATH supports O_TMPFILE.  Exit with
     257     status 77 (unsupported) if the kernel does not support O_TMPFILE.
     258     Even with kernel support, not all file systems O_TMPFILE, so return
     259     true if the directory supports O_TMPFILE, false if not.  */
     260  static bool
     261  probe_path (const char *path)
     262  {
     263    int fd = openat (AT_FDCWD, path, O_TMPFILE | O_RDWR, 0);
     264    if (fd < 0)
     265      {
     266        if (errno == EISDIR)
     267          /* The system does not support O_TMPFILE.  */
     268          {
     269            printf ("info: kernel does not support O_TMPFILE\n");
     270            exit (77);
     271          }
     272        if (errno == EOPNOTSUPP)
     273          {
     274            printf ("info: path does not support O_TMPFILE: %s\n", path);
     275            return false;
     276          }
     277        printf ("error: openat (\"%s\", O_TMPFILE | O_RDWR): %m\n", path);
     278        exit (1);
     279      }
     280    close (fd);
     281    return true;
     282  }
     283  
     284  static int
     285  do_test (void)
     286  {
     287    umask (0);
     288    const char *paths[] = { ".", "/dev/shm", "/tmp",
     289                            getenv ("TEST_TMPFILE_PATH"),
     290                            NULL };
     291    bool supported = false;
     292    for (int i = 0; paths[i] != NULL; ++i)
     293      if (probe_path (paths[i]))
     294        {
     295          supported = true;
     296          check_wrapper ("open", wrap_open, paths[i]);
     297          check_wrapper ("openat", wrap_openat, paths[i]);
     298          check_wrapper ("open64", wrap_open64, paths[i]);
     299          check_wrapper ("openat64", wrap_openat64, paths[i]);
     300        }
     301  
     302    if (!supported)
     303      return 77;
     304  
     305    return 0;
     306  }
     307  
     308  #else  /* !O_TMPFILE */
     309  
     310  static int
     311  do_test (void)
     312  {
     313    return 77;
     314  }
     315  
     316  #endif  /* O_TMPFILE */
     317  
     318  #include <support/test-driver.c>