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