/* Copyright (C) 2021 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2018 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2014 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1997 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Extra share flags that can be indicated by the user */ int __djgpp_share_flags; /* Move a file descriptor FD such that it is at least MIN_FD. If the file descriptor is changed (meaning it was origially *below* MIN_FD), the old one will be closed. If the operation failed (no more handles available?), -1 will be returned, in which case the original descriptor is still valid. This jewel is due to Morten Welinder . */ static int move_fd(int fd, int min_fd) { int new_fd, tmp_fd; if (fd == -1 || fd >= min_fd) return fd; tmp_fd = dup(fd); if (tmp_fd == -1) return tmp_fd; new_fd = move_fd(tmp_fd, min_fd); if (new_fd != -1) close(fd); /* success--get rid of the original descriptor */ else close(tmp_fd); /* failure--get rid of the temporary descriptor */ return new_fd; } static int opendir_as_fd(const char *filename, const int oflag) { int fd, old_fd, flags, ret; /* Check the flags. */ if ((oflag & (O_RDONLY|O_WRONLY|O_RDWR)) != O_RDONLY) { /* Only read-only access is allowed. */ errno = EISDIR; return -1; } /* * Allocate a file descriptor that: * * - is dup'd off nul, so that bogus operations at least go somewhere * sensible; * - is in binary mode; * - is non-inheritable; * - is marked as a directory. * * __FSEXT_alloc_fd() conveniently handles the first two. File handles * greater than 19 are not inherited, due to a misfeature * of the DOS exec call. */ old_fd = __FSEXT_alloc_fd(NULL); if (old_fd < 0) return -1; /* Pass through errno. */ fd = move_fd(old_fd, 20); if (fd < 0) { close(old_fd); errno = EMFILE; return -1; } __set_fd_properties(fd, filename, 0); __set_fd_flags(fd, FILE_DESC_DIRECTORY); flags = fcntl(fd, F_GETFD); if (flags < 0) return -1; /* Pass through errno. */ flags |= FD_CLOEXEC; ret = fcntl(fd, F_SETFD, flags); if (ret < 0) return -1; /* Pass through errno. */ return fd; } int open(const char* filename, int oflag, ...) { const int original_oflag = oflag; int fd, dmode, bintext, dont_have_share; char real_name[FILENAME_MAX + 1]; int should_create = (oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); int dirs_solved = 0; /* Only directories resolved in real_name? */ int permissions; va_list ap; va_start(ap, oflag); permissions = va_arg(ap, int); va_end(ap); /* Solve symlinks and honor O_NOLINK flag */ if (oflag & O_NOLINK) { if (!__solve_dir_symlinks(filename, real_name)) return -1; /* errno from from __solve_dir_symlinks() */ dirs_solved = 1; } else { if (!__solve_symlinks(filename, real_name)) return -1; /* errno from from __solve_symlinks() */ } /* Honor O_NOFOLLOW flag. */ if (oflag & O_NOFOLLOW) { /* O_NOFOLLOW, as defined in glibc, requires open() to fail if the * last path component is a symlink. However, it still requires to * resolve all other path components. * We check if there were any symlinks by comparing __solve_symlinks() * input and output. That function does not perform any path * canonicalization so it should be safe. */ if (strcmp(filename, real_name)) { /* Yes, there were symlinks in the path. Now take all but the last * path component from `real_name', add last path component from * `filename', and try to resolve that mess. */ char temp[FILENAME_MAX + 1]; char resolved[2]; char *last_separator; int old_errno = errno; strcpy(temp, real_name); last_separator = basename(temp); *last_separator = '\0'; last_separator = basename(filename); strcat(temp, "/"); strcat(temp, last_separator); if ((readlink(temp, resolved, 1) != -1) || (errno != EINVAL)) { /* Yes, the last path component was a symlink. */ errno = ELOOP; return -1; } errno = old_errno; } } /* Check this up front, to reduce cost and minimize effect */ if (should_create) { /* Symlink: We're not allowed to follow a symlink, when creating a file * with the same name as the symlink. */ char temp_name[FILENAME_MAX + 1]; #define IRD_BUF_SIZE FILENAME_MAX + 1 char ird_buf[IRD_BUF_SIZE]; const size_t ird_bufsize = IRD_BUF_SIZE; #undef IRD_BUF_SIZE if (!dirs_solved) { if (!__solve_dir_symlinks(filename, temp_name)) return -1; /* errno from from __solve_dir_symlinks() */ } else strcpy(temp_name, real_name); if (__internal_readlink(temp_name, 0, ird_buf, ird_bufsize) < 0) { /* If the error is something other than "doesn't exist" * or "isn't a symlink", return it. */ if ((errno != ENOENT) && (errno != EINVAL)) return -1; /* errno from __internal_readlink() */ } else { /* It's a symlink. */ errno = EEXIST; return -1; } /* Normal file */ if (__file_exists(real_name)) { /* file exists and we didn't want it to */ errno = EEXIST; return -1; } } /* figure out what mode we're opening the file in */ bintext = oflag & (O_TEXT | O_BINARY); if (!bintext) bintext = _fmode & (O_TEXT | O_BINARY); if (!bintext) bintext = O_BINARY; /* DOS doesn't want to see these bits */ oflag &= ~(O_TEXT | O_BINARY); dmode = (permissions & S_IWUSR) ? 0 : 1; /* Merge the share flags if they are specified */ dont_have_share = ((oflag & (SH_DENYNO | SH_DENYRW | SH_DENYRD | SH_DENYWR)) == 0); if (dont_have_share && __djgpp_share_flags) { dont_have_share = 0; oflag |= __djgpp_share_flags; } if (should_create) fd = _creatnew(real_name, dmode, oflag & 0xff); else { fd = _open(real_name, oflag); if (fd == -1) { /* It doesn't make sense to try anything else if there are no more file handles available. */ if (errno == EMFILE || errno == ENFILE) return fd; if (__file_exists(real_name)) { /* Under multi-taskers, such as Windows, our file might be open by some other program with DENY-NONE sharing bit, which fails the `_open' call above. Try again with DENY-NONE bit set, unless some sharing bits were already set in the initial call. */ if (dont_have_share) fd = _open(real_name, oflag | SH_DENYNO); } /* Don't call _creat on existing files for which _open fails, since the file could be truncated as a result. */ else if ((oflag & O_CREAT)) { fd = _creat(real_name, dmode); if (fd == -1 && _os_trueversion == 0x532) { /* For Windows 2000/XP, calling _creat will create one file more even if the file descriptors have already becoming exhausted. This means that the created file will exist in the directory but will have a file descriptor of -1 and thus becoming inaccessible. To avoid this it will be deleted here. The calling of _creat for the last possible file descriptor, this is the highst file descriptor that is possible, will already rise EMFILE. */ if (errno == EMFILE || errno == ENFILE) { int previous_errno = errno; remove(real_name); errno = previous_errno; return fd; } } } } } /* Is the target a directory? If so, generate a file descriptor * for the directory. Skip the rest of `open', because it does not * apply to directories. */ if ((fd == -1) && (access(real_name, D_OK) == 0)) return opendir_as_fd(real_name, original_oflag); if (fd == -1) return fd; /* errno already set by _open or _creat */ if ((oflag & O_TRUNC) && !should_create) #ifndef TRUNC_CHECK _write(fd, 0, 0); #else /* Windows 2000/XP will fail 0 byte writes (truncate) on a character device (nul, con) if opened with lfn calls. We can either ignore the return completely or ignore errors on NT. Since a truncate fail should never happen (and if it does we expect an error on the next write) this probably doesn't make much difference. */ if (_write(fd, 0, 0) < 0 && _os_trueversion != 0x532) { _close(fd); return -1; } #endif /* we do this last because _open and _create set it also. */ /* force setmode() to do ioctl() for cooked/raw */ __file_handle_set(fd, bintext ^ (O_BINARY|O_TEXT)); /* this will do cooked/raw ioctl() on character devices */ setmode(fd, bintext); __set_fd_properties(fd, real_name, oflag); if (oflag & O_APPEND) { llseek(fd, 0, SEEK_END); } return fd; }