Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ Note: We're only listing outstanding class updates.
Ruby-related constants. This module was reserved in Ruby 3.4
and is now officially defined. [[Feature #20884]]

* Ruby::Box

* A new (experimental) feature to provide separation about definitions.
For the detail of "Ruby Box", see [doc/language/box.md](doc/language/box.md).
[[Feature #21311]] [[Misc #21385]]

* Set

* `Set` is now a core class, instead of an autoloaded stdlib class.
Expand Down Expand Up @@ -232,8 +238,8 @@ The following default gem is added.

The following default gems are updated.

* RubyGems 4.0.0
* bundler 4.0.0
* RubyGems 4.0.1
* bundler 4.0.1
* date 3.5.0
* digest 3.2.1
* english 0.8.1
Expand Down Expand Up @@ -427,8 +433,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
[Feature #21262]: https://bugs.ruby-lang.org/issues/21262
[Feature #21275]: https://bugs.ruby-lang.org/issues/21275
[Feature #21287]: https://bugs.ruby-lang.org/issues/21287
[Feature #21311]: https://bugs.ruby-lang.org/issues/21311
[Feature #21347]: https://bugs.ruby-lang.org/issues/21347
[Feature #21360]: https://bugs.ruby-lang.org/issues/21360
[Misc #21385]: https://bugs.ruby-lang.org/issues/21385
[Feature #21389]: https://bugs.ruby-lang.org/issues/21389
[Feature #21390]: https://bugs.ruby-lang.org/issues/21390
[Feature #21459]: https://bugs.ruby-lang.org/issues/21459
Expand Down
208 changes: 116 additions & 92 deletions box.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@

#include <stdio.h>

#ifdef HAVE_SYS_SENDFILE_H
# include <sys/sendfile.h>
#endif
#ifdef HAVE_COPYFILE_H
#include <copyfile.h>
#endif

VALUE rb_cBox = 0;
VALUE rb_cBoxEntry = 0;
VALUE rb_mBoxLoader = 0;
Expand Down Expand Up @@ -518,10 +525,21 @@ sprint_ext_filename(char *str, size_t size, long box_id, const char *prefix, con
return snprintf(str, size, "%s%s%sp%"PRI_PIDT_PREFIX"u_%ld_%s", tmp_dir, DIRSEP, prefix, getpid(), box_id, basename);
}

#ifdef _WIN32
enum copy_error_type {
COPY_ERROR_NONE,
COPY_ERROR_SRC_OPEN,
COPY_ERROR_DST_OPEN,
COPY_ERROR_SRC_READ,
COPY_ERROR_DST_WRITE,
COPY_ERROR_SRC_STAT,
COPY_ERROR_DST_CHMOD,
COPY_ERROR_SYSERR
};

static const char *
copy_ext_file_error(char *message, size_t size)
copy_ext_file_error(char *message, size_t size, int copy_retvalue)
{
#ifdef _WIN32
int error = GetLastError();
char *p = message;
size_t len = snprintf(message, size, "%d: ", error);
Expand All @@ -536,120 +554,130 @@ copy_ext_file_error(char *message, size_t size)
if (*p == '\n' || *p == '\r')
*p = ' ';
}
return message;
}
#else
static const char *
copy_ext_file_error(char *message, size_t size, int copy_retvalue, const char *src_path, const char *dst_path)
{
switch (copy_retvalue) {
case 1:
snprintf(message, size, "can't open the extension path: %s", src_path);
case COPY_ERROR_SRC_OPEN:
strlcpy(message, "can't open the extension path", size);
break;
case COPY_ERROR_DST_OPEN:
strlcpy(message, "can't open the file to write", size);
break;
case 2:
snprintf(message, size, "can't open the file to write: %s", dst_path);
case COPY_ERROR_SRC_READ:
strlcpy(message, "failed to read the extension path", size);
break;
case 3:
snprintf(message, size, "failed to read the extension path: %s", src_path);
case COPY_ERROR_DST_WRITE:
strlcpy(message, "failed to write the extension path", size);
break;
case 4:
snprintf(message, size, "failed to write the extension path: %s", dst_path);
case COPY_ERROR_SRC_STAT:
strlcpy(message, "failed to stat the extension path to copy permissions", size);
break;
case 5:
snprintf(message, size, "failed to stat the extension path to copy permissions: %s", src_path);
case COPY_ERROR_DST_CHMOD:
strlcpy(message, "failed to set permissions to the copied extension path", size);
break;
case 6:
snprintf(message, size, "failed to set permissions to the copied extension path: %s", dst_path);
case COPY_ERROR_SYSERR:
strlcpy(message, strerror(errno), size);
break;
case COPY_ERROR_NONE: /* shouldn't be called */
default:
rb_bug("unknown return value of copy_ext_file: %d", copy_retvalue);
}
#endif
return message;
}

#ifndef _WIN32
static enum copy_error_type
copy_stream(int src_fd, int dst_fd)
{
char buffer[1024];
ssize_t rsize;

while ((rsize = read(src_fd, buffer, sizeof(buffer))) != 0) {
if (rsize < 0) return COPY_ERROR_SRC_READ;
for (size_t written = 0; written < (size_t)rsize;) {
ssize_t wsize = write(dst_fd, buffer+written, rsize-written);
if (wsize < 0) return COPY_ERROR_DST_WRITE;
written += (size_t)wsize;
}
}
return COPY_ERROR_NONE;
}
#endif

static int
static enum copy_error_type
copy_ext_file(const char *src_path, const char *dst_path)
{
#if defined(_WIN32)
int rvalue;

WCHAR *w_src = rb_w32_mbstr_to_wstr(CP_UTF8, src_path, -1, NULL);
WCHAR *w_dst = rb_w32_mbstr_to_wstr(CP_UTF8, dst_path, -1, NULL);
if (!w_src || !w_dst) {
free(w_src);
free(w_dst);
rb_memerror();
}

rvalue = CopyFileW(w_src, w_dst, FALSE) ? 0 : 1;
enum copy_error_type rvalue = CopyFileW(w_src, w_dst, TRUE) ?
COPY_ERROR_NONE : COPY_ERROR_SYSERR;
free(w_src);
free(w_dst);
return rvalue;
#else
FILE *src, *dst;
char buffer[1024];
size_t read = 0, wrote, written = 0;
size_t maxread = sizeof(buffer);
int eof = 0;
int clean_read = 1;
int retvalue = 0;

src = fopen(src_path, "rb");
if (!src) {
return 1;
# ifdef O_BINARY
const int bin = O_BINARY;
# else
const int bin = 0;
# endif
const int src_fd = open(src_path, O_RDONLY|bin);
if (src_fd < 0) return COPY_ERROR_SRC_OPEN;

struct stat src_st;
if (fstat(src_fd, &src_st)) {
close(src_fd);
return COPY_ERROR_SRC_STAT;
}
dst = fopen(dst_path, "wb");
if (!dst) {
return 2;

const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|bin, S_IRWXU);
if (dst_fd < 0) {
close(src_fd);
return COPY_ERROR_DST_OPEN;
}
while (!eof) {
if (clean_read) {
read = fread(buffer, 1, sizeof(buffer), src);
written = 0;
}
if (read > 0) {
wrote = fwrite(buffer+written, 1, read-written, dst);
if (wrote < read-written) {
if (ferror(dst)) {
retvalue = 4;
break;
}
else { // partial write
clean_read = 0;
written += wrote;
}
}
else { // Wrote the entire buffer to dst, next read is clean one
clean_read = 1;
}
}
if (read < maxread) {
if (clean_read && feof(src)) {
// If it's not clean, buffer should have bytes not written yet.
eof = 1;
}
else if (ferror(src)) {
retvalue = 3;
// Writes could be partial/dirty, but this load is failure anyway
break;
}
}

enum copy_error_type ret = COPY_ERROR_NONE;

if (fchmod(dst_fd, src_st.st_mode & 0777)) {
ret = COPY_ERROR_DST_CHMOD;
goto done;
}
fclose(src);
fclose(dst);
#if defined(__CYGWIN__)
// On Cygwin, CopyFile-like operations may strip executable bits.
// Explicitly match destination file permissions to source.
if (retvalue == 0) {
struct stat st;
if (stat(src_path, &st) != 0) {
retvalue = 5;
}
else if (chmod(dst_path, st.st_mode & 0777) != 0) {
retvalue = 6;
}

const size_t count_max = (SIZE_MAX >> 1) + 1;
(void)count_max;

# ifdef HAVE_COPY_FILE_RANGE
for (;;) {
ssize_t written = copy_file_range(src_fd, NULL, dst_fd, NULL, count_max, 0);
if (written == 0) goto done;
if (written < 0) break;
}
#endif
return retvalue;
# endif
# ifdef HAVE_FCOPYFILE
if (fcopyfile(src_fd, dst_fd, NULL, COPYFILE_DATA) == 0) {
goto done;
}
# endif
# ifdef USE_SENDFILE
for (;;) {
ssize_t written = sendfile(src_fd, dst_fd, NULL count_max);
if (written == 0) goto done;
if (written < 0) break;
}
# endif
ret = copy_stream(src_fd, dst_fd);

done:
close(src_fd);
if (dst_fd >= 0) close(dst_fd);
if (ret != COPY_ERROR_NONE) unlink(dst_path);
return ret;
#endif
}

Expand Down Expand Up @@ -700,7 +728,7 @@ VALUE
rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path)
{
char ext_path[MAXPATHLEN], fname2[MAXPATHLEN], basename[MAXPATHLEN];
int copy_error, wrote;
int wrote;
const char *src_path = RSTRING_PTR(path), *fname_ptr = RSTRING_PTR(fname);
rb_box_t *box = rb_get_box_t(box_value);

Expand All @@ -711,15 +739,11 @@ rb_box_local_extension(VALUE box_value, VALUE fname, VALUE path)
if (wrote >= (int)sizeof(ext_path)) {
rb_bug("Extension file path in the box was too long");
}
copy_error = copy_ext_file(src_path, ext_path);
enum copy_error_type copy_error = copy_ext_file(src_path, ext_path);
if (copy_error) {
char message[1024];
#if defined(_WIN32)
copy_ext_file_error(message, sizeof(message));
#else
copy_ext_file_error(message, sizeof(message), copy_error, src_path, ext_path);
#endif
rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %s): %s", ext_path, src_path, message);
copy_ext_file_error(message, sizeof(message), copy_error);
rb_raise(rb_eLoadError, "can't prepare the extension file for Ruby Box (%s from %"PRIsVALUE"): %s", ext_path, path, message);
}
// TODO: register the path to be clean-uped
return rb_str_new_cstr(ext_path);
Expand Down
2 changes: 1 addition & 1 deletion concurrent_set.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ concurrent_set_try_resize_without_locking(VALUE old_set_obj, VALUE *set_obj_ptr)
new_capacity = old_capacity;
}

// May cause GC and therefore deletes, so must hapen first.
// May cause GC and therefore deletes, so must happen first.
VALUE new_set_obj = rb_concurrent_set_new(old_set->funcs, new_capacity);
struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj);

Expand Down
8 changes: 3 additions & 5 deletions doc/language/box.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Ruby Box - Ruby's in-process separation of Classes and Modules

Ruby Box is designed to provide separated spaces in a Ruby process, to isolate applications and libraries.
Ruby Box is designed to provide separated spaces in a Ruby process, to isolate application codes, libraries and monkey patches.

## Known issues

Expand All @@ -12,13 +12,11 @@ Ruby Box is designed to provide separated spaces in a Ruby process, to isolate a
## TODOs

* Add the loaded namespace on iseq to check if another namespace tries running the iseq (add a field only when VM_CHECK_MODE?)
* Delete per-box extension files (.so) lazily or process exit
* Collect `rb_classext_t` entries for a box when GC collects the box
* Delete per-box extension files (.so) lazily or process exit (on Windows)
* Assign its own TOPLEVEL_BINDING in boxes
* Fix calling `warn` in boxes to refer `$VERBOSE` and `Warning.warn` in the box
* Make an internal data container `Ruby::Box::Entry` invisible
* Make an internal data container class `Ruby::Box::Entry` invisible
* More test cases about `$LOAD_PATH` and `$LOADED_FEATURES`
* Return classpath and nesting without the namespace prefix in the namespace itself [#21316](https://bugs.ruby-lang.org/issues/21316), [#21318](https://bugs.ruby-lang.org/issues/21318)

## How to use

Expand Down
2 changes: 1 addition & 1 deletion gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1816,7 +1816,7 @@ id2ref_tbl_mark(void *data)
// It's very unlikely, but if enough object ids were generated, keys may be T_BIGNUM
rb_mark_set(table);
}
// We purposedly don't mark values, as they are weak references.
// We purposely don't mark values, as they are weak references.
// rb_gc_obj_free_vm_weak_references takes care of cleaning them up.
}

Expand Down
2 changes: 1 addition & 1 deletion gc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module GC
# interleaved with program execution both before the method returns and afterward;
# therefore sweeping may not be completed before the return.
#
# Note that these keword arguments are implementation- and version-dependent,
# Note that these keyword arguments are implementation- and version-dependent,
# are not guaranteed to be future-compatible,
# and may be ignored in some implementations.
def self.start full_mark: true, immediate_mark: true, immediate_sweep: true
Expand Down
6 changes: 6 additions & 0 deletions jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,12 @@ rb_jit_fix_mod_fix(VALUE recv, VALUE obj)
return rb_fix_mod_fix(recv, obj);
}

VALUE
rb_jit_fix_div_fix(VALUE recv, VALUE obj)
{
return rb_fix_div_fix(recv, obj);
}

// YJIT/ZJIT need this function to never allocate and never raise
VALUE
rb_yarv_str_eql_internal(VALUE str1, VALUE str2)
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: false

module Bundler
VERSION = "4.0.0".freeze
VERSION = "4.0.1".freeze

def self.bundler_major_version
@bundler_major_version ||= gem_version.segments.first
Expand Down
2 changes: 1 addition & 1 deletion lib/erb/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
require 'cgi/escape'

# Load or define ERB::Escape#html_escape.
# We don't build the C extention 'cgi/escape' for JRuby, TruffleRuby, and WASM.
# We don't build the C extension 'cgi/escape' for JRuby, TruffleRuby, and WASM.
# miniruby (used by CRuby build scripts) also fails to load erb/escape.so.
begin
require 'erb/escape'
Expand Down
Loading