summaryrefslogtreecommitdiff
path: root/lib/stat.c
blob: 7e7c2f6f887fc8f0805c80e77d76f70bc8020243 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* Work around platform bugs in stat.
   Copyright (C) 2009-2017 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* written by Eric Blake */

/* If the user's config.h happens to include <sys/stat.h>, let it include only
   the system's <sys/stat.h> here, so that orig_stat doesn't recurse to
   rpl_stat.  */
#define __need_system_sys_stat_h
#include <config.h>

/* Get the original definition of stat.  It might be defined as a macro.  */
#include <sys/types.h>
#include <sys/stat.h>
#undef __need_system_sys_stat_h

#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
# if _GL_WINDOWS_64_BIT_ST_SIZE
#  undef stat /* avoid warning on mingw64 with _FILE_OFFSET_BITS=64 */
#  define stat _stati64
#  define REPLACE_FUNC_STAT_DIR 1
#  undef REPLACE_FUNC_STAT_FILE
# elif REPLACE_FUNC_STAT_FILE
/* mingw64 has a broken stat() function, based on _stat(), in libmingwex.a.
   Bypass it.  */
#  define stat _stat
#  define REPLACE_FUNC_STAT_DIR 1
#  undef REPLACE_FUNC_STAT_FILE
# endif
#endif

static int
orig_stat (const char *filename, struct stat *buf)
{
  return stat (filename, buf);
}

/* Specification.  */
/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
   eliminates this include because of the preliminary #include <sys/stat.h>
   above.  */
#include "sys/stat.h"

#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include "dosname.h"
#include "verify.h"

#if REPLACE_FUNC_STAT_DIR
# include "pathmax.h"
  /* The only known systems where REPLACE_FUNC_STAT_DIR is needed also
     have a constant PATH_MAX.  */
# ifndef PATH_MAX
#  error "Please port this replacement to your platform"
# endif
#endif

/* Store information about NAME into ST.  Work around bugs with
   trailing slashes.  Mingw has other bugs (such as st_ino always
   being 0 on success) which this wrapper does not work around.  But
   at least this implementation provides the ability to emulate fchdir
   correctly.  */

int
rpl_stat (char const *name, struct stat *st)
{
  int result = orig_stat (name, st);
#if REPLACE_FUNC_STAT_FILE
  /* Solaris 9 mistakenly succeeds when given a non-directory with a
     trailing slash.  */
  if (result == 0 && !S_ISDIR (st->st_mode))
    {
      size_t len = strlen (name);
      if (ISSLASH (name[len - 1]))
        {
          errno = ENOTDIR;
          return -1;
        }
    }
#endif /* REPLACE_FUNC_STAT_FILE */
#if REPLACE_FUNC_STAT_DIR

  if (result == -1 && errno == ENOENT)
    {
      /* Due to mingw's oddities, there are some directories (like
         c:\) where stat() only succeeds with a trailing slash, and
         other directories (like c:\windows) where stat() only
         succeeds without a trailing slash.  But we want the two to be
         synonymous, since chdir() manages either style.  Likewise, Mingw also
         reports ENOENT for names longer than PATH_MAX, when we want
         ENAMETOOLONG, and for stat("file/"), when we want ENOTDIR.
         Fortunately, mingw PATH_MAX is small enough for stack
         allocation.  */
      char fixed_name[PATH_MAX + 1] = {0};
      size_t len = strlen (name);
      bool check_dir = false;
      verify (PATH_MAX <= 4096);
      if (PATH_MAX <= len)
        errno = ENAMETOOLONG;
      else if (len)
        {
          strcpy (fixed_name, name);
          if (ISSLASH (fixed_name[len - 1]))
            {
              check_dir = true;
              while (len && ISSLASH (fixed_name[len - 1]))
                fixed_name[--len] = '\0';
              if (!len)
                fixed_name[0] = '/';
            }
          else
            fixed_name[len++] = '/';
          result = orig_stat (fixed_name, st);
          if (result == 0 && check_dir && !S_ISDIR (st->st_mode))
            {
              result = -1;
              errno = ENOTDIR;
            }
        }
    }
#endif /* REPLACE_FUNC_STAT_DIR */
  return result;
}