Skip to content

Commit 6003c14

Browse files
committed
Avoid crashing when opendir fails without setting errno
It's still unclear what is happening, but multiple users did report this case, so this is a workaround for what so far appear to be a bug in some `libc`. The scanning will still fail, but with a clean error including the faulty directory, rather than a full VM crash.
1 parent 92edbf7 commit 6003c14

1 file changed

Lines changed: 38 additions & 4 deletions

File tree

ext/bootsnap/bootsnap.c

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include <sys/stat.h>
2222
#include <dirent.h>
2323

24+
#ifndef RBIMPL_ATTR_NORETURN
25+
#define RBIMPL_ATTR_NORETURN()
26+
#endif
27+
2428
#ifdef __APPLE__
2529
// The symbol is present, however not in the headers
2630
// See: https://github.com/rails/bootsnap/issues/470
@@ -152,6 +156,21 @@ bs_rb_get_path(VALUE self, VALUE fname)
152156
}
153157

154158
#ifdef HAVE_FSTATAT
159+
160+
RBIMPL_ATTR_NORETURN()
161+
static void
162+
bs_syserr_fail_path(const char *func_name, int n, VALUE path)
163+
{
164+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
165+
}
166+
167+
RBIMPL_ATTR_NORETURN()
168+
static void
169+
bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
170+
{
171+
rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
172+
}
173+
155174
static VALUE
156175
bs_rb_scan_dir(VALUE self, VALUE abspath)
157176
{
@@ -167,7 +186,17 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
167186
if (errno == ENOTDIR || errno == ENOENT) {
168187
return result;
169188
}
170-
rb_sys_fail("opendir");
189+
190+
// BUG: Some users reported a crash here because Ruby's syserr trigger
191+
// a crash if called with `errno == 0`.
192+
// The opendir spec is quite clear that if it returns NULL, then `errno` must
193+
// be set, and yet here we are.
194+
// So turning no errno into EINVAL, and from there I hope to get to the bottom of things.
195+
if (errno == 0) {
196+
errno = EINVAL;
197+
}
198+
199+
bs_syserr_fail_path("opendir", errno, abspath);
171200
return Qundef;
172201
}
173202

@@ -185,17 +214,22 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
185214
if (dfd < 0) {
186215
dfd = dirfd(dirp);
187216
if (dfd < 0) {
188-
rb_sys_fail("dirfd");
217+
int err = errno;
218+
closedir(dirp);
219+
bs_syserr_fail_path("dirfd", err, abspath);
189220
return Qundef;
190221
}
191222
}
192223

193224
if (fstatat(dfd, entry->d_name, &st, 0)) {
194225
if (errno == ENOENT) {
195226
// Broken symlinK
227+
errno = 0;
196228
continue;
197229
}
198-
rb_sys_fail("fstatat");
230+
int err = errno;
231+
closedir(dirp);
232+
bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
199233
return Qundef;
200234
}
201235

@@ -226,7 +260,7 @@ bs_rb_scan_dir(VALUE self, VALUE abspath)
226260
}
227261

228262
if (closedir(dirp)) {
229-
rb_sys_fail("closedir");
263+
bs_syserr_fail_path("closedir", errno, abspath);
230264
return Qundef;
231265
}
232266
return result;

0 commit comments

Comments
 (0)