module Bootsnap::LoadPathCache::Native

Public Class Methods

scan_dir(p1) click to toggle source
static VALUE
bs_rb_scan_dir(VALUE self, VALUE abspath)
{
    Check_Type(abspath, T_STRING);

    VALUE dirs = rb_ary_new();
    VALUE requirables = rb_ary_new();
    VALUE result = rb_ary_new_from_args(2, requirables, dirs);

    DIR *dirp = opendir(RSTRING_PTR(abspath));
    if (dirp == NULL) {
        if (errno == ENOTDIR || errno == ENOENT) {
            return result;
        }

        bs_syserr_fail_path("opendir", errno, abspath);
        return Qundef;
    }

    struct dirent *entry;
    struct stat st;
    int dfd = -1;

    while (1) {
        errno = 0;

        entry = readdir(dirp);
        if (entry == NULL) break;

        if (entry->d_name[0] == '.') continue;

        if (RB_UNLIKELY(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)) {
            // Note: the original implementation of LoadPathCache did follow symlink.
            // So this is replicated here, but I'm not sure it's a good idea.
            if (dfd < 0) {
                dfd = dirfd(dirp);
                if (dfd < 0) {
                    int err = errno;
                    closedir(dirp);
                    bs_syserr_fail_path("dirfd", err, abspath);
                    return Qundef;
                }
            }

            if (fstatat(dfd, entry->d_name, &st, 0)) {
                if (errno == ENOENT) continue; // Broken symlink

                int err = errno;
                closedir(dirp);
                bs_syserr_fail_dir_entry("fstatat", err, abspath, entry->d_name);
                return Qundef;
            }

            if (S_ISREG(st.st_mode)) {
                entry->d_type = DT_REG;
            } else if (S_ISDIR(st.st_mode)) {
                entry->d_type = DT_DIR;
            }
        }

        if (entry->d_type == DT_DIR) {
            rb_ary_push(dirs, rb_utf8_str_new_cstr(entry->d_name));
            continue;
        } else if (entry->d_type == DT_REG) {
            size_t len = strlen(entry->d_name);
            bool is_requirable = (
                // Comparing 4B allows compiler to optimize this into a single 32b integer comparison.
                (len > 3 && memcmp(entry->d_name + (len - 3), ".rb", 4) == 0)
                || (len > DLEXT_MAXLEN && memcmp(entry->d_name + (len - DLEXT_MAXLEN), DLEXT, DLEXT_MAXLEN + 1) == 0)
#ifdef DLEXT2
                || (len > DLEXT2_MAXLEN && memcmp(entry->d_name + (len - DLEXT2_MAXLEN), DLEXT2, DLEXT2_MAXLEN + 1) == 0)
#endif
            );
            if (is_requirable) {
                rb_ary_push(requirables, rb_utf8_str_new(entry->d_name, len));
            }
        }
    }

    if (errno) {
      int err = errno;
      closedir(dirp);
      bs_syserr_fail_path("readdir", err, abspath);
    } else if (closedir(dirp)) {
        bs_syserr_fail_path("closedir", errno, abspath);
        return Qundef;
    }

    return result;
}