diff options
Diffstat (limited to 'gl/open.c')
| -rw-r--r-- | gl/open.c | 131 |
1 files changed, 79 insertions, 52 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-2026 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 |
| @@ -38,13 +38,7 @@ orig_open (const char *filename, int flags, mode_t mode) | |||
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | /* Specification. */ | 40 | /* Specification. */ |
| 41 | #ifdef __osf__ | 41 | #include <fcntl.h> |
| 42 | /* Write "fcntl.h" here, not <fcntl.h>, otherwise OSF/1 5.1 DTK cc eliminates | ||
| 43 | this include because of the preliminary #include <fcntl.h> above. */ | ||
| 44 | # include "fcntl.h" | ||
| 45 | #else | ||
| 46 | # include <fcntl.h> | ||
| 47 | #endif | ||
| 48 | 42 | ||
| 49 | #include "cloexec.h" | 43 | #include "cloexec.h" |
| 50 | 44 | ||
| @@ -55,24 +49,29 @@ orig_open (const char *filename, int flags, mode_t mode) | |||
| 55 | #include <sys/stat.h> | 49 | #include <sys/stat.h> |
| 56 | #include <unistd.h> | 50 | #include <unistd.h> |
| 57 | 51 | ||
| 52 | #ifndef HAVE_WORKING_O_DIRECTORY | ||
| 53 | # define HAVE_WORKING_O_DIRECTORY false | ||
| 54 | #endif | ||
| 55 | |||
| 56 | #ifndef OPEN_TRAILING_SLASH_BUG | ||
| 57 | # define OPEN_TRAILING_SLASH_BUG false | ||
| 58 | #endif | ||
| 59 | |||
| 58 | #ifndef REPLACE_OPEN_DIRECTORY | 60 | #ifndef REPLACE_OPEN_DIRECTORY |
| 59 | # define REPLACE_OPEN_DIRECTORY 0 | 61 | # define REPLACE_OPEN_DIRECTORY false |
| 60 | #endif | 62 | #endif |
| 61 | 63 | ||
| 64 | static int | ||
| 65 | lstatif (char const *filename, struct stat *st, int flags) | ||
| 66 | { | ||
| 67 | return flags & O_NOFOLLOW ? lstat (filename, st) : stat (filename, st); | ||
| 68 | } | ||
| 69 | |||
| 62 | int | 70 | int |
| 63 | open (const char *filename, int flags, ...) | 71 | open (const char *filename, int flags, ...) |
| 64 | { | 72 | { |
| 65 | /* 0 = unknown, 1 = yes, -1 = no. */ | 73 | 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 | 74 | ||
| 75 | mode = 0; | ||
| 76 | if (flags & O_CREAT) | 75 | if (flags & O_CREAT) |
| 77 | { | 76 | { |
| 78 | va_list arg; | 77 | va_list arg; |
| @@ -95,11 +94,10 @@ open (const char *filename, int flags, ...) | |||
| 95 | #endif | 94 | #endif |
| 96 | 95 | ||
| 97 | #if defined _WIN32 && ! defined __CYGWIN__ | 96 | #if defined _WIN32 && ! defined __CYGWIN__ |
| 98 | if (strcmp (filename, "/dev/null") == 0) | 97 | if (streq (filename, "/dev/null")) |
| 99 | filename = "NUL"; | 98 | filename = "NUL"; |
| 100 | #endif | 99 | #endif |
| 101 | 100 | ||
| 102 | #if OPEN_TRAILING_SLASH_BUG | ||
| 103 | /* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename | 101 | /* 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 | 102 | 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>: | 103 | <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>: |
| @@ -118,21 +116,55 @@ open (const char *filename, int flags, ...) | |||
| 118 | directories, | 116 | directories, |
| 119 | - if O_WRONLY or O_RDWR is specified, open() must fail because the | 117 | - if O_WRONLY or O_RDWR is specified, open() must fail because the |
| 120 | file does not contain a '.' directory. */ | 118 | file does not contain a '.' directory. */ |
| 121 | if ((flags & O_CREAT) | 119 | bool check_for_slash_bug; |
| 122 | || (flags & O_ACCMODE) == O_RDWR | 120 | if (OPEN_TRAILING_SLASH_BUG) |
| 123 | || (flags & O_ACCMODE) == O_WRONLY) | ||
| 124 | { | 121 | { |
| 125 | size_t len = strlen (filename); | 122 | size_t len = strlen (filename); |
| 126 | if (len > 0 && filename[len - 1] == '/') | 123 | check_for_slash_bug = len && filename[len - 1] == '/'; |
| 124 | } | ||
| 125 | else | ||
| 126 | check_for_slash_bug = false; | ||
| 127 | |||
| 128 | if (check_for_slash_bug | ||
| 129 | && (flags & O_CREAT | ||
| 130 | || (flags & O_ACCMODE) == O_RDWR | ||
| 131 | || (flags & O_ACCMODE) == O_WRONLY)) | ||
| 132 | { | ||
| 133 | errno = EISDIR; | ||
| 134 | return -1; | ||
| 135 | } | ||
| 136 | |||
| 137 | /* With the trailing slash bug or without working O_DIRECTORY, check with | ||
| 138 | stat first lest we hang trying to open a fifo. Although there is | ||
| 139 | a race between this and opening the file, we can do no better. | ||
| 140 | After opening the file we will check again with fstat. */ | ||
| 141 | bool check_directory = | ||
| 142 | (check_for_slash_bug | ||
| 143 | || (!HAVE_WORKING_O_DIRECTORY && flags & O_DIRECTORY)); | ||
| 144 | if (check_directory) | ||
| 145 | { | ||
| 146 | struct stat statbuf; | ||
| 147 | if (lstatif (filename, &statbuf, flags) < 0) | ||
| 127 | { | 148 | { |
| 128 | errno = EISDIR; | 149 | if (! (flags & O_CREAT && errno == ENOENT)) |
| 150 | return -1; | ||
| 151 | } | ||
| 152 | else if (!S_ISDIR (statbuf.st_mode)) | ||
| 153 | { | ||
| 154 | errno = ENOTDIR; | ||
| 129 | return -1; | 155 | return -1; |
| 130 | } | 156 | } |
| 131 | } | 157 | } |
| 158 | |||
| 159 | /* 0 = unknown, 1 = yes, -1 = no. */ | ||
| 160 | #if GNULIB_defined_O_CLOEXEC | ||
| 161 | int have_cloexec = -1; | ||
| 162 | #else | ||
| 163 | static int have_cloexec; | ||
| 132 | #endif | 164 | #endif |
| 133 | 165 | ||
| 134 | fd = orig_open (filename, | 166 | int fd = orig_open (filename, |
| 135 | flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode); | 167 | flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode); |
| 136 | 168 | ||
| 137 | if (flags & O_CLOEXEC) | 169 | if (flags & O_CLOEXEC) |
| 138 | { | 170 | { |
| @@ -154,19 +186,21 @@ open (const char *filename, int flags, ...) | |||
| 154 | #if REPLACE_FCHDIR | 186 | #if REPLACE_FCHDIR |
| 155 | /* Implementing fchdir and fdopendir requires the ability to open a | 187 | /* Implementing fchdir and fdopendir requires the ability to open a |
| 156 | directory file descriptor. If open doesn't support that (as on | 188 | directory file descriptor. If open doesn't support that (as on |
| 157 | mingw), we use a dummy file that behaves the same as directories | 189 | mingw), use a dummy file that behaves the same as directories |
| 158 | on Linux (ie. always reports EOF on attempts to read()), and | 190 | 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 | 191 | override fstat in fchdir.c to hide the dummy. */ |
| 160 | dummy. */ | ||
| 161 | if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES | 192 | if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES |
| 162 | && ((flags & O_ACCMODE) == O_RDONLY | 193 | && ((flags & (O_ACCMODE | O_CREAT)) == O_RDONLY |
| 163 | || (O_SEARCH != O_RDONLY && (flags & O_ACCMODE) == O_SEARCH))) | 194 | || (O_SEARCH != O_RDONLY |
| 195 | && (flags & (O_ACCMODE | O_CREAT)) == O_SEARCH))) | ||
| 164 | { | 196 | { |
| 165 | struct stat statbuf; | 197 | struct stat statbuf; |
| 166 | if (stat (filename, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)) | 198 | if (check_directory |
| 199 | || (lstatif (filename, &statbuf, flags) == 0 | ||
| 200 | && S_ISDIR (statbuf.st_mode))) | ||
| 167 | { | 201 | { |
| 168 | /* Maximum recursion depth of 1. */ | 202 | /* Maximum recursion depth of 1. */ |
| 169 | fd = open ("/dev/null", flags, mode); | 203 | fd = open ("/dev/null", flags & ~O_DIRECTORY, mode); |
| 170 | if (0 <= fd) | 204 | if (0 <= fd) |
| 171 | fd = _gl_register_fd (fd, filename); | 205 | fd = _gl_register_fd (fd, filename); |
| 172 | } | 206 | } |
| @@ -175,10 +209,8 @@ open (const char *filename, int flags, ...) | |||
| 175 | } | 209 | } |
| 176 | #endif | 210 | #endif |
| 177 | 211 | ||
| 178 | #if OPEN_TRAILING_SLASH_BUG | 212 | /* 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, | 213 | 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>: | 214 | <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 | 215 | "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 | 216 | ends with one or more trailing <slash> characters shall not be resolved |
| @@ -186,23 +218,18 @@ open (const char *filename, int flags, ...) | |||
| 186 | <slash> characters names an existing directory" | 218 | <slash> characters names an existing directory" |
| 187 | If the named file without the slash is not a directory, open() must fail | 219 | If the named file without the slash is not a directory, open() must fail |
| 188 | with ENOTDIR. */ | 220 | with ENOTDIR. */ |
| 189 | if (fd >= 0) | 221 | if (check_directory && 0 <= fd) |
| 190 | { | 222 | { |
| 191 | /* We know len is positive, since open did not fail with ENOENT. */ | 223 | struct stat statbuf; |
| 192 | size_t len = strlen (filename); | 224 | int r = fstat (fd, &statbuf); |
| 193 | if (filename[len - 1] == '/') | 225 | if (r < 0 || !S_ISDIR (statbuf.st_mode)) |
| 194 | { | 226 | { |
| 195 | struct stat statbuf; | 227 | int err = r < 0 ? errno : ENOTDIR; |
| 196 | 228 | close (fd); | |
| 197 | if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode)) | 229 | errno = err; |
| 198 | { | 230 | return -1; |
| 199 | close (fd); | ||
| 200 | errno = ENOTDIR; | ||
| 201 | return -1; | ||
| 202 | } | ||
| 203 | } | 231 | } |
| 204 | } | 232 | } |
| 205 | #endif | ||
| 206 | 233 | ||
| 207 | #if REPLACE_FCHDIR | 234 | #if REPLACE_FCHDIR |
| 208 | if (!REPLACE_OPEN_DIRECTORY && 0 <= fd) | 235 | if (!REPLACE_OPEN_DIRECTORY && 0 <= fd) |
