diff options
Diffstat (limited to 'gl/open.c')
| -rw-r--r-- | gl/open.c | 121 |
1 files changed, 77 insertions, 44 deletions
| @@ -1,5 +1,5 @@ | |||
| 1 | /* Open a descriptor to a file. | 1 | /* Open a descriptor to a file. |
| 2 | Copyright (C) 2007-2024 Free Software Foundation, Inc. | 2 | Copyright (C) 2007-2025 Free Software Foundation, Inc. |
| 3 | 3 | ||
| 4 | This file is free software: you can redistribute it and/or modify | 4 | This file is free software: you can redistribute it and/or modify |
| 5 | it under the terms of the GNU Lesser General Public License as | 5 | it under the terms of the GNU Lesser General Public License as |
| @@ -55,24 +55,29 @@ orig_open (const char *filename, int flags, mode_t mode) | |||
| 55 | #include <sys/stat.h> | 55 | #include <sys/stat.h> |
| 56 | #include <unistd.h> | 56 | #include <unistd.h> |
| 57 | 57 | ||
| 58 | #ifndef HAVE_WORKING_O_DIRECTORY | ||
| 59 | # define HAVE_WORKING_O_DIRECTORY false | ||
| 60 | #endif | ||
| 61 | |||
| 62 | #ifndef OPEN_TRAILING_SLASH_BUG | ||
| 63 | # define OPEN_TRAILING_SLASH_BUG false | ||
| 64 | #endif | ||
| 65 | |||
| 58 | #ifndef REPLACE_OPEN_DIRECTORY | 66 | #ifndef REPLACE_OPEN_DIRECTORY |
| 59 | # define REPLACE_OPEN_DIRECTORY 0 | 67 | # define REPLACE_OPEN_DIRECTORY false |
| 60 | #endif | 68 | #endif |
| 61 | 69 | ||
| 70 | static int | ||
| 71 | lstatif (char const *filename, struct stat *st, int flags) | ||
| 72 | { | ||
| 73 | return flags & O_NOFOLLOW ? lstat (filename, st) : stat (filename, st); | ||
| 74 | } | ||
| 75 | |||
| 62 | int | 76 | int |
| 63 | open (const char *filename, int flags, ...) | 77 | open (const char *filename, int flags, ...) |
| 64 | { | 78 | { |
| 65 | /* 0 = unknown, 1 = yes, -1 = no. */ | 79 | mode_t mode = 0; |
| 66 | #if GNULIB_defined_O_CLOEXEC | ||
| 67 | int have_cloexec = -1; | ||
| 68 | #else | ||
| 69 | static int have_cloexec; | ||
| 70 | #endif | ||
| 71 | |||
| 72 | mode_t mode; | ||
| 73 | int fd; | ||
| 74 | 80 | ||
| 75 | mode = 0; | ||
| 76 | if (flags & O_CREAT) | 81 | if (flags & O_CREAT) |
| 77 | { | 82 | { |
| 78 | va_list arg; | 83 | va_list arg; |
| @@ -99,7 +104,6 @@ open (const char *filename, int flags, ...) | |||
| 99 | filename = "NUL"; | 104 | filename = "NUL"; |
| 100 | #endif | 105 | #endif |
| 101 | 106 | ||
| 102 | #if OPEN_TRAILING_SLASH_BUG | ||
| 103 | /* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename | 107 | /* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename |
| 104 | ends in a slash, as POSIX says such a filename must name a directory | 108 | ends in a slash, as POSIX says such a filename must name a directory |
| 105 | <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>: | 109 | <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>: |
| @@ -118,21 +122,55 @@ open (const char *filename, int flags, ...) | |||
| 118 | directories, | 122 | directories, |
| 119 | - if O_WRONLY or O_RDWR is specified, open() must fail because the | 123 | - if O_WRONLY or O_RDWR is specified, open() must fail because the |
| 120 | file does not contain a '.' directory. */ | 124 | file does not contain a '.' directory. */ |
| 121 | if ((flags & O_CREAT) | 125 | bool check_for_slash_bug; |
| 122 | || (flags & O_ACCMODE) == O_RDWR | 126 | if (OPEN_TRAILING_SLASH_BUG) |
| 123 | || (flags & O_ACCMODE) == O_WRONLY) | ||
| 124 | { | 127 | { |
| 125 | size_t len = strlen (filename); | 128 | size_t len = strlen (filename); |
| 126 | if (len > 0 && filename[len - 1] == '/') | 129 | check_for_slash_bug = len && filename[len - 1] == '/'; |
| 130 | } | ||
| 131 | else | ||
| 132 | check_for_slash_bug = false; | ||
| 133 | |||
| 134 | if (check_for_slash_bug | ||
| 135 | && (flags & O_CREAT | ||
| 136 | || (flags & O_ACCMODE) == O_RDWR | ||
| 137 | || (flags & O_ACCMODE) == O_WRONLY)) | ||
| 138 | { | ||
| 139 | errno = EISDIR; | ||
| 140 | return -1; | ||
| 141 | } | ||
| 142 | |||
| 143 | /* With the trailing slash bug or without working O_DIRECTORY, check with | ||
| 144 | stat first lest we hang trying to open a fifo. Although there is | ||
| 145 | a race between this and opening the file, we can do no better. | ||
| 146 | After opening the file we will check again with fstat. */ | ||
| 147 | bool check_directory = | ||
| 148 | (check_for_slash_bug | ||
| 149 | || (!HAVE_WORKING_O_DIRECTORY && flags & O_DIRECTORY)); | ||
| 150 | if (check_directory) | ||
| 151 | { | ||
| 152 | struct stat statbuf; | ||
| 153 | if (lstatif (filename, &statbuf, flags) < 0) | ||
| 154 | { | ||
| 155 | if (! (flags & O_CREAT && errno == ENOENT)) | ||
| 156 | return -1; | ||
| 157 | } | ||
| 158 | else if (!S_ISDIR (statbuf.st_mode)) | ||
| 127 | { | 159 | { |
| 128 | errno = EISDIR; | 160 | errno = ENOTDIR; |
| 129 | return -1; | 161 | return -1; |
| 130 | } | 162 | } |
| 131 | } | 163 | } |
| 164 | |||
| 165 | /* 0 = unknown, 1 = yes, -1 = no. */ | ||
| 166 | #if GNULIB_defined_O_CLOEXEC | ||
| 167 | int have_cloexec = -1; | ||
| 168 | #else | ||
| 169 | static int have_cloexec; | ||
| 132 | #endif | 170 | #endif |
| 133 | 171 | ||
| 134 | fd = orig_open (filename, | 172 | int fd = orig_open (filename, |
| 135 | flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode); | 173 | flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode); |
| 136 | 174 | ||
| 137 | if (flags & O_CLOEXEC) | 175 | if (flags & O_CLOEXEC) |
| 138 | { | 176 | { |
| @@ -154,19 +192,21 @@ open (const char *filename, int flags, ...) | |||
| 154 | #if REPLACE_FCHDIR | 192 | #if REPLACE_FCHDIR |
| 155 | /* Implementing fchdir and fdopendir requires the ability to open a | 193 | /* Implementing fchdir and fdopendir requires the ability to open a |
| 156 | directory file descriptor. If open doesn't support that (as on | 194 | directory file descriptor. If open doesn't support that (as on |
| 157 | mingw), we use a dummy file that behaves the same as directories | 195 | mingw), use a dummy file that behaves the same as directories |
| 158 | on Linux (ie. always reports EOF on attempts to read()), and | 196 | on Linux (ie. always reports EOF on attempts to read()), and |
| 159 | override fstat() in fchdir.c to hide the fact that we have a | 197 | override fstat in fchdir.c to hide the dummy. */ |
| 160 | dummy. */ | ||
| 161 | if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES | 198 | if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES |
| 162 | && ((flags & O_ACCMODE) == O_RDONLY | 199 | && ((flags & (O_ACCMODE | O_CREAT)) == O_RDONLY |
| 163 | || (O_SEARCH != O_RDONLY && (flags & O_ACCMODE) == O_SEARCH))) | 200 | || (O_SEARCH != O_RDONLY |
| 201 | && (flags & (O_ACCMODE | O_CREAT)) == O_SEARCH))) | ||
| 164 | { | 202 | { |
| 165 | struct stat statbuf; | 203 | struct stat statbuf; |
| 166 | if (stat (filename, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)) | 204 | if (check_directory |
| 205 | || (lstatif (filename, &statbuf, flags) == 0 | ||
| 206 | && S_ISDIR (statbuf.st_mode))) | ||
| 167 | { | 207 | { |
| 168 | /* Maximum recursion depth of 1. */ | 208 | /* Maximum recursion depth of 1. */ |
| 169 | fd = open ("/dev/null", flags, mode); | 209 | fd = open ("/dev/null", flags & ~O_DIRECTORY, mode); |
| 170 | if (0 <= fd) | 210 | if (0 <= fd) |
| 171 | fd = _gl_register_fd (fd, filename); | 211 | fd = _gl_register_fd (fd, filename); |
| 172 | } | 212 | } |
| @@ -175,10 +215,8 @@ open (const char *filename, int flags, ...) | |||
| 175 | } | 215 | } |
| 176 | #endif | 216 | #endif |
| 177 | 217 | ||
| 178 | #if OPEN_TRAILING_SLASH_BUG | 218 | /* If checking for directories, fail if fd does not refer to a directory. |
| 179 | /* If the filename ends in a slash and fd does not refer to a directory, | 219 | Rationale: A filename ending in slash cannot name a non-directory |
| 180 | then fail. | ||
| 181 | Rationale: POSIX says such a filename must name a directory | ||
| 182 | <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>: | 220 | <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>: |
| 183 | "A pathname that contains at least one non-<slash> character and that | 221 | "A pathname that contains at least one non-<slash> character and that |
| 184 | ends with one or more trailing <slash> characters shall not be resolved | 222 | ends with one or more trailing <slash> characters shall not be resolved |
| @@ -186,23 +224,18 @@ open (const char *filename, int flags, ...) | |||
| 186 | <slash> characters names an existing directory" | 224 | <slash> characters names an existing directory" |
| 187 | If the named file without the slash is not a directory, open() must fail | 225 | If the named file without the slash is not a directory, open() must fail |
| 188 | with ENOTDIR. */ | 226 | with ENOTDIR. */ |
| 189 | if (fd >= 0) | 227 | if (check_directory && 0 <= fd) |
| 190 | { | 228 | { |
| 191 | /* We know len is positive, since open did not fail with ENOENT. */ | 229 | struct stat statbuf; |
| 192 | size_t len = strlen (filename); | 230 | int r = fstat (fd, &statbuf); |
| 193 | if (filename[len - 1] == '/') | 231 | if (r < 0 || !S_ISDIR (statbuf.st_mode)) |
| 194 | { | 232 | { |
| 195 | struct stat statbuf; | 233 | int err = r < 0 ? errno : ENOTDIR; |
| 196 | 234 | close (fd); | |
| 197 | if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode)) | 235 | errno = err; |
| 198 | { | 236 | return -1; |
| 199 | close (fd); | ||
| 200 | errno = ENOTDIR; | ||
| 201 | return -1; | ||
| 202 | } | ||
| 203 | } | 237 | } |
| 204 | } | 238 | } |
| 205 | #endif | ||
| 206 | 239 | ||
| 207 | #if REPLACE_FCHDIR | 240 | #if REPLACE_FCHDIR |
| 208 | if (!REPLACE_OPEN_DIRECTORY && 0 <= fd) | 241 | if (!REPLACE_OPEN_DIRECTORY && 0 <= fd) |
