(root)/
findutils-4.9.0/
gnulib-tests/
test-getcwd.c
       1  /* Test of getcwd() function.
       2     Copyright (C) 2009-2022 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  #include <config.h>
      18  
      19  #include <unistd.h>
      20  
      21  #include <errno.h>
      22  #include <fcntl.h>
      23  #include <limits.h>
      24  #include <stdio.h>
      25  #include <stdlib.h>
      26  #include <string.h>
      27  #include <sys/stat.h>
      28  
      29  #include "pathmax.h"
      30  #include "qemu.h"
      31  #include "macros.h"
      32  
      33  #if !(HAVE_GETPAGESIZE || defined getpagesize)
      34  # define getpagesize() 0
      35  #endif
      36  
      37  /* This size is chosen to be larger than PATH_MAX (4k), yet smaller than
      38     the 16kB pagesize on ia64 linux.  Those conditions make the code below
      39     trigger a bug in glibc's getcwd implementation before 2.4.90-10.  */
      40  #define TARGET_LEN (5 * 1024)
      41  
      42  #if defined HAVE_OPENAT || (defined GNULIB_OPENAT && defined HAVE_FDOPENDIR)
      43  # define HAVE_OPENAT_SUPPORT 1
      44  #else
      45  # define HAVE_OPENAT_SUPPORT 0
      46  #endif
      47  
      48  /* Keep this test in sync with m4/getcwd-abort-bug.m4.  */
      49  static int
      50  test_abort_bug (void)
      51  {
      52    char *cwd;
      53    size_t initial_cwd_len;
      54    int fail = 0;
      55  
      56    /* The bug is triggered when PATH_MAX < getpagesize (), so skip
      57       this relatively expensive and invasive test if that's not true.  */
      58  #ifdef PATH_MAX
      59    int bug_possible = PATH_MAX < getpagesize ();
      60  #else
      61    int bug_possible = 0;
      62  #endif
      63    if (! bug_possible)
      64      return 0;
      65  
      66    cwd = getcwd (NULL, 0);
      67    if (cwd == NULL)
      68      return 2;
      69  
      70    initial_cwd_len = strlen (cwd);
      71    free (cwd);
      72  
      73    if (HAVE_OPENAT_SUPPORT)
      74      {
      75        static char const dir_name[] = "confdir-14B---";
      76        size_t desired_depth = ((TARGET_LEN - 1 - initial_cwd_len)
      77                                / sizeof dir_name);
      78        size_t d;
      79        for (d = 0; d < desired_depth; d++)
      80          {
      81            if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
      82              {
      83                if (! (errno == ERANGE || errno == ENAMETOOLONG
      84                       || errno == ENOENT))
      85                  fail = 3; /* Unable to construct deep hierarchy.  */
      86                break;
      87              }
      88          }
      89  
      90        /* If libc has the bug in question, this invocation of getcwd
      91           results in a failed assertion.  */
      92        cwd = getcwd (NULL, 0);
      93        if (cwd == NULL)
      94          fail = 4; /* getcwd didn't assert, but it failed for a long name
      95                       where the answer could have been learned.  */
      96        free (cwd);
      97  
      98        /* Call rmdir first, in case the above chdir failed.  */
      99        rmdir (dir_name);
     100        while (0 < d--)
     101          {
     102            if (chdir ("..") < 0)
     103              {
     104                fail = 5;
     105                break;
     106              }
     107            rmdir (dir_name);
     108          }
     109      }
     110  
     111    return fail;
     112  }
     113  
     114  /* The length of this name must be 8.  */
     115  #define DIR_NAME "confdir3"
     116  #define DIR_NAME_LEN 8
     117  #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
     118  
     119  /* The length of "../".  */
     120  #define DOTDOTSLASH_LEN 3
     121  
     122  /* Leftover bytes in the buffer, to work around library or OS bugs.  */
     123  #define BUF_SLOP 20
     124  
     125  /* Keep this test in sync with m4/getcwd-path-max.m4.  */
     126  static int
     127  test_long_name (void)
     128  {
     129  #ifndef PATH_MAX
     130    /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
     131       at least not on a local file system.  And if we were to start worrying
     132       about remote file systems, we'd have to enable the wrapper function
     133       all of the time, just to be safe.  That's not worth the cost.  */
     134    return 0;
     135  #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
     136          - DIR_NAME_SIZE - BUF_SLOP) \
     137         <= PATH_MAX)
     138    /* FIXME: Assuming there's a system for which this is true,
     139       this should be done in a compile test.  */
     140    return 0;
     141  #else
     142    /* For a process running under QEMU user-mode, the "/" directory is not
     143       really the root directory, but the value of the QEMU_LD_PREFIX environment
     144       variable or of the -L command-line option.  This causes the logic from
     145       glibc/sysdeps/posix/getcwd.c to fail.  In this case, skip the test.  */
     146    if (is_running_under_qemu_user ())
     147      return 77;
     148  
     149    char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
     150             + DIR_NAME_SIZE + BUF_SLOP];
     151    char *cwd = getcwd (buf, PATH_MAX);
     152    size_t initial_cwd_len;
     153    size_t cwd_len;
     154    int fail = 0;
     155    size_t n_chdirs = 0;
     156  
     157    if (cwd == NULL)
     158      return 1;
     159  
     160    cwd_len = initial_cwd_len = strlen (cwd);
     161  
     162    while (1)
     163      {
     164  # ifdef HAVE_GETCWD_SHORTER
     165        /* On OS/X <= 10.9 for example, we're restricted to shorter paths
     166           as lstat() doesn't support more than PATH_MAX.  */
     167        size_t dotdot_max = PATH_MAX * 2;
     168  # else
     169        size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
     170  # endif
     171        char *c = NULL;
     172  
     173        cwd_len += DIR_NAME_SIZE;
     174        /* If mkdir or chdir fails, it could be that this system cannot create
     175           any file with an absolute name longer than PATH_MAX, such as cygwin.
     176           If so, leave fail as 0, because the current working directory can't
     177           be too long for getcwd if it can't even be created.  On Linux with
     178           the 9p file system, mkdir fails with error EINVAL when cwd_len gets
     179           too long; ignore this failure because the getcwd() system call
     180           produces good results whereas the gnulib substitute calls getdents64
     181           which fails with error EPROTO.
     182           For other errors, be pessimistic and consider that as a failure,
     183           too.  */
     184        if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
     185          {
     186            if (! (errno == ERANGE || errno == ENAMETOOLONG || errno == ENOENT))
     187              #ifdef __linux__
     188              if (! (errno == EINVAL))
     189              #endif
     190                fail = 2;
     191            break;
     192          }
     193  
     194        if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
     195          {
     196            c = getcwd (buf, PATH_MAX);
     197            if (!c && errno == ENOENT)
     198              {
     199                fail = 3;
     200                break;
     201              }
     202            if (c)
     203              {
     204                fail = 4;
     205                break;
     206              }
     207            if (! (errno == ERANGE || errno == ENAMETOOLONG))
     208              {
     209                fail = 5;
     210                break;
     211              }
     212          }
     213  
     214        if (dotdot_max <= cwd_len - initial_cwd_len)
     215          {
     216            if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
     217              break;
     218            c = getcwd (buf, cwd_len + 1);
     219            if (!c)
     220              {
     221                if (! (errno == ERANGE || errno == ENOENT
     222                       || errno == ENAMETOOLONG))
     223                  {
     224                    fail = 6;
     225                    break;
     226                  }
     227                if (HAVE_OPENAT_SUPPORT || errno == ERANGE || errno == ENOENT)
     228                  {
     229                    fail = 7;
     230                    break;
     231                  }
     232              }
     233          }
     234  
     235        if (c && strlen (c) != cwd_len)
     236          {
     237            fail = 8;
     238            break;
     239          }
     240        ++n_chdirs;
     241      }
     242  
     243    /* Leaving behind such a deep directory is not polite.
     244       So clean up here, right away, even though the driving
     245       shell script would also clean up.  */
     246    {
     247      size_t i;
     248  
     249      /* Try rmdir first, in case the chdir failed.  */
     250      rmdir (DIR_NAME);
     251      for (i = 0; i <= n_chdirs; i++)
     252        {
     253          if (chdir ("..") < 0)
     254            break;
     255          if (rmdir (DIR_NAME) != 0)
     256            break;
     257        }
     258    }
     259  
     260    return fail;
     261  #endif
     262  }
     263  
     264  int
     265  main (int argc, char **argv)
     266  {
     267    int err1 = test_abort_bug ();
     268    int err2 = test_long_name ();
     269    return err1 * 10 + (err1 != 0 && err2 == 77 ? 0 : err2);
     270  }