-
Notifications
You must be signed in to change notification settings - Fork 43
Various bug fixes throughout the code base #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -431,14 +431,13 @@ static int _positive_int_parse(const char *str) { | |
} | ||
|
||
#if defined(__linux__) | ||
// Linux-specific version that uses syscalls directly and doesn't allocate heap memory. | ||
// Safe to use after vfork() and before execve() | ||
static int _highest_possibly_open_fd_dir_linux(const char *fd_dir) { | ||
int highest_fd_so_far = 0; | ||
/// Set `FD_CLOEXEC` on all open file descriptors listed under `fd_dir` so | ||
/// they are automatically closed upon `execve()`. | ||
/// Safe to use after `vfork()` and before `execve()` | ||
static void _set_cloexec_to_open_fds(const char *fd_dir) { | ||
int dir_fd = open(fd_dir, O_RDONLY); | ||
if (dir_fd < 0) { | ||
// errno set by `open`. | ||
return -1; | ||
return; | ||
} | ||
|
||
// Buffer for directory entries - allocated on stack, no heap allocation | ||
|
@@ -450,49 +449,37 @@ static int _highest_possibly_open_fd_dir_linux(const char *fd_dir) { | |
if (errno == EINTR) { | ||
continue; | ||
} else { | ||
// `errno` set by _getdents64. | ||
highest_fd_so_far = -1; | ||
close(dir_fd); | ||
return highest_fd_so_far; | ||
return; | ||
} | ||
} | ||
if (bytes_read == 0) { | ||
close(dir_fd); | ||
return highest_fd_so_far; | ||
return; | ||
} | ||
long offset = 0; | ||
while (offset < bytes_read) { | ||
struct linux_dirent64 *entry = (struct linux_dirent64 *)(buffer + offset); | ||
|
||
// Skip "." and ".." entries | ||
if (entry->d_name[0] != '.') { | ||
int number = _positive_int_parse(entry->d_name); | ||
if (number > highest_fd_so_far) { | ||
highest_fd_so_far = number; | ||
int fd = _positive_int_parse(entry->d_name); | ||
if (fd > STDERR_FILENO && fd != dir_fd) { | ||
int flags = fcntl(fd, F_GETFD); | ||
if (flags >= 0) { | ||
// Set FD_CLOEXEC on every open fd so they are closed after exec() | ||
fcntl(fd, F_SETFD, flags | FD_CLOEXEC); | ||
} | ||
} | ||
} | ||
|
||
offset += entry->d_reclen; | ||
} | ||
} | ||
|
||
close(dir_fd); | ||
return highest_fd_so_far; | ||
} | ||
#endif | ||
|
||
// This function is only used on systems with Linux kernel 5.9 or lower. | ||
// On newer systems, `close_range` is used instead. | ||
// This function is only used on non-Linux systems. | ||
static int _highest_possibly_open_fd(void) { | ||
#if defined(__linux__) | ||
int hi = _highest_possibly_open_fd_dir_linux("/dev/fd"); | ||
if (hi < 0) { | ||
hi = sysconf(_SC_OPEN_MAX); | ||
} | ||
#else | ||
int hi = sysconf(_SC_OPEN_MAX); | ||
#endif | ||
return hi; | ||
return sysconf(_SC_OPEN_MAX); | ||
} | ||
|
||
int _subprocess_fork_exec( | ||
|
@@ -681,8 +668,8 @@ int _subprocess_fork_exec( | |
errno = ENOSYS; | ||
#if (__has_include(<linux/close_range.h>) && (!defined(__ANDROID__) || __ANDROID_API__ >= 34)) || defined(__FreeBSD__) | ||
// We must NOT close pipefd[1] for writing errors | ||
rc = close_range(STDERR_FILENO + 1, pipefd[1] - 1, 0); | ||
rc |= close_range(pipefd[1] + 1, ~0U, 0); | ||
rc = close_range(STDERR_FILENO + 1, pipefd[1] - 1, CLOSE_RANGE_CLOEXEC); | ||
rc |= close_range(pipefd[1] + 1, ~0U, CLOSE_RANGE_CLOEXEC); | ||
#elif defined(__OpenBSD__) | ||
// OpenBSD Supports closefrom, but not close_range | ||
// See https://man.openbsd.org/closefrom | ||
|
@@ -692,13 +679,22 @@ int _subprocess_fork_exec( | |
rc = closefrom(pipefd[1] + 1); | ||
#endif | ||
if (rc != 0) { | ||
// close_range failed (or doesn't exist), fall back to close() | ||
for (int fd = STDERR_FILENO + 1; fd <= _highest_possibly_open_fd(); fd++) { | ||
#if defined(__linux__) | ||
iCharlesHu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_set_cloexec_to_open_fds("/dev/fd"); | ||
#else | ||
// close_range failed (or doesn't exist), fall back to setting FD_CLOEXEC | ||
int highest_open_fd = _highest_possibly_open_fd(); | ||
for (int fd = STDERR_FILENO + 1; fd <= highest_open_fd; fd++) { | ||
// We must NOT close pipefd[1] for writing errors | ||
if (fd != pipefd[1]) { | ||
close(fd); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto to above: it seems the general consensus is to use |
||
int flags = fcntl(fd, F_GETFD); | ||
if (flags >= 0) { | ||
// Set FD_CLOEXEC on every open fd so they are closed after exec() | ||
fcntl(fd, F_SETFD, flags | FD_CLOEXEC); | ||
} | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
// Finally, exec | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're doing a syscall anyway, why don't you just close it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For two reasons:
FD_CLOEXEC
is preferred for this situation:https://utcc.utoronto.ca/~cks/space/blog/unix/ForkFDsAndRaces
https://docs.fedoraproject.org/en-US/defensive-coding/tasks/Tasks-Descriptors/#sect-Defensive_Coding-Tasks-Descriptors-Child_Processes
close()
while traversing/dev/fd
beforeclose()
modifies that directory.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of those articles I think are really talking about the pre-fork case. I think in our specific situation where we've already called fork (and thus have exactly one thread and no concurrency considerations), and we can guarantee that we will not call fork again, and that we will call exec imminently, there's no practical difference (that I can see).
This seems like a great point though!
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should not happen and doesn't happen in the original code from
swift-sdk-generator
. How the original code works is:That's completely fine. What is not fine (but that's not what the original code does) is to close whilst iterating of course.