diff --git a/doc/distribution/windows.md b/doc/distribution/windows.md
index 26a727d7adb26a..304e3451547fc6 100644
--- a/doc/distribution/windows.md
+++ b/doc/distribution/windows.md
@@ -149,6 +149,11 @@ sh ../../ruby/configure -C --disable-install-doc --with-opt-dir=C:\Users\usernam
of `cmd.exe`. If you want to enable it explicitly, run `cmd.exe` with
`/E:ON` option.
+7. Some tests in `nmake check` create symbolic links. Enable
+ [Developer Mode](https://learn.microsoft.com/windows/apps/get-started/developer-mode-features-and-debugging)
+ (Settings > System > For developers) so that they do not fail with
+ `Permission denied @ rb_file_s_symlink`.
+
### How to compile and install
1. Execute `win32\configure.bat` on your build directory.
diff --git a/file.c b/file.c
index 1231848c1035f0..5473ee910b4e4a 100644
--- a/file.c
+++ b/file.c
@@ -1837,14 +1837,24 @@ rb_file_pipe_p(VALUE obj, VALUE fname)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * File.symlink?(filepath) -> true or false
+ * File.symlink?(path) -> true or false
*
- * Returns +true+ if +filepath+ points to a symbolic link, +false+ otherwise:
+ * Returns whether the entry at `path` is a symbolic link:
*
- * symlink = File.symlink('t.txt', 'symlink')
- * File.symlink?('symlink') # => true
- * File.symlink?('t.txt') # => false
+ * ```ruby
+ * # Create paths.
+ * file_path = 'doc/extension.rdoc' # => "doc/extension.rdoc"
+ * target_path = File.join('..', file_path) # => "../doc/extension.rdoc"
+ * link_path = 'lib/u.tmp' # => "lib/u.tmp"
+ * File.symlink?(link_path) # => false
+ * # Create link and verify.
+ * File.symlink(target_path, link_path)
+ * File.symlink?(link_path) # => true
+ * File.delete(link_path) # Clean up.
+ * ```
*
*/
@@ -3511,15 +3521,27 @@ rb_file_s_link(VALUE klass, VALUE from, VALUE to)
#ifdef HAVE_SYMLINK
/*
+ * :markup: markdown
+ *
* call-seq:
- * File.symlink(old_name, new_name) -> 0
+ * File.symlink(path, link_path) -> 0
*
- * Creates a symbolic link called new_name for the existing file
- * old_name. Raises a NotImplemented exception on
- * platforms that do not support symbolic links.
+ * Not supported on some platforms.
*
- * File.symlink("testfile", "link2test") #=> 0
+ * Creates a symbolic link at `link_path` to the entry at `path`:
*
+ * ```ruby
+ * # Create paths.
+ * file_path = 'doc/extension.rdoc' # => "doc/extension.rdoc"
+ * target_path = File.join('..', file_path) # => "../doc/extension.rdoc"
+ * link_path = 'lib/u.tmp' # => "lib/u.tmp"
+ * # Create link and verify.
+ * File.symlink(target_path, link_path)
+ * File.read(file_path) == File.read(link_path) # => true
+ * File.delete(link_path) # Clean up.
+ * ```
+ *
+ * See also: ::read, ::readlink, ::symlink?.
*/
static VALUE
@@ -3541,14 +3563,23 @@ rb_file_s_symlink(VALUE klass, VALUE from, VALUE to)
#ifdef HAVE_READLINK
/*
+ * :markup: markdown
+ *
* call-seq:
- * File.readlink(link_name) -> file_name
+ * File.readlink(link_path) -> path
*
- * Returns the name of the file referenced by the given link.
- * Not available on all platforms.
+ * Returns the string path to the entry referenced by the given `link_path`:
+ *
+ * ```ruby
+ * # Create paths.
+ * file_path = 'doc/extension.rdoc' # => "doc/extension.rdoc"
+ * target_path = File.join('..', file_path) # => "../doc/extension.rdoc"
+ * link_path = 'lib/u.tmp' # => "lib/u.tmp"
+ * File.symlink(target_path, link_path)
+ * File.readlink(link_path) # => "../doc/extension.rdoc"
+ * File.delete(link_path) # Clean up.
+ * ```
*
- * File.symlink("testfile", "link2test") #=> 0
- * File.readlink("link2test") #=> "testfile"
*/
static VALUE
@@ -6415,18 +6446,24 @@ rb_stat_p(VALUE obj)
}
/*
+ * :markup: markdown
+ *
* call-seq:
- * stat.symlink? -> true or false
+ * symlink? -> true or false
*
- * Returns true if stat is a symbolic link,
- * false if it isn't or if the operating system doesn't
- * support this feature. As File::stat automatically follows symbolic
- * links, #symlink? will always be false for an object
- * returned by File::stat.
+ * Returns whether the entry in `self` is a symbolic link:
*
- * File.symlink("testfile", "alink") #=> 0
- * File.stat("alink").symlink? #=> false
- * File.lstat("alink").symlink? #=> true
+ * ```ruby
+ * path = 'doc/t.tmp'
+ * link_path = 'lib/u.tmp'
+ * File.write(path, 'foo')
+ * File.symlink(path, link_path)
+ * File.stat(path).symlink? # => false
+ * File.stat(link_path).symlink? # Raises Errno::ENOENT; entry is not a file.
+ * File.lstat(link_path).symlink? # => true
+ * File.delete(path)
+ * File.delete(link_path)
+ * ```
*
*/
diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb
index 32767c6a79081d..c21b8e67193e64 100644
--- a/lib/rubygems/util/atomic_file_writer.rb
+++ b/lib/rubygems/util/atomic_file_writer.rb
@@ -26,6 +26,16 @@ def self.open(file_name)
basename = File.basename(file_name)
tmp_path = File.join(dirname, ".#{basename.byteslice(0, 254 - tmp_suffix.bytesize)}#{tmp_suffix}")
+ # The temporary name is longer than the final one, so on Windows a
+ # writable destination can still map to a path beyond the 260-character
+ # MAX_PATH limit. Only in that case, trim the random suffix just enough to
+ # fit, keeping at least 8 hex characters to avoid collisions.
+ if tmp_path.length >= 260 && Gem.win_platform?
+ keep = [tmp_suffix.bytesize - (tmp_path.length - 259), ".tmp.".bytesize + 8].max
+ tmp_suffix = tmp_suffix.byteslice(0, keep)
+ tmp_path = File.join(dirname, ".#{basename.byteslice(0, 254 - tmp_suffix.bytesize)}#{tmp_suffix}")
+ end
+
flags = File::RDWR | File::CREAT | File::EXCL | File::BINARY
flags |= File::SHARE_DELETE if defined?(File::SHARE_DELETE)
diff --git a/pathname_builtin.rb b/pathname_builtin.rb
index b12432cfb286fe..9e510eddb239a9 100644
--- a/pathname_builtin.rb
+++ b/pathname_builtin.rb
@@ -1196,7 +1196,37 @@ def birthtime() File.birthtime(@path) end
#
def ctime() File.ctime(@path) end
- # See File.mtime. Returns last modification time.
+ # :markup: markdown
+ #
+ # call-seq:
+ # mtime -> time
+ #
+ # Returns a Time object containing the time of the most recent
+ # modification to the entry represented by `self`;
+ # see {File System Timestamps}[rdoc-ref:file/timestamps.md]:
+ #
+ # ```ruby
+ # # A directory and its Pathname.
+ # dir_path = 'doc/foo'
+ # dir_pn = Pathname(dir_path)
+ # # Create directory; directory mtime established.
+ # dir_pn.mkdir
+ # dir_pn.mtime # => 2026-06-28 16:38:02.675780521 -0500
+ # # A file therein and its Pathname.
+ # file_path = dir_pn.join('t.tmp')
+ # file_pn = Pathname(file_path)
+ # # Create file; file mtime established; directory mtime updated.
+ # file_pn.write('foo')
+ # dir_pn.mtime # => 2026-06-28 16:41:23.107750483 -0500
+ # file_pn.mtime # => 2026-06-28 16:41:23.107750483 -0500
+ # # Modify file; file mtime updated; directory mtime unchanged.
+ # file_pn.write('bar')
+ # dir_pn.mtime # => 2026-06-28 16:41:23.107750483 -0500
+ # file_pn.mtime # => 2026-06-28 16:42:48.869163049 -0500
+ # # Clean up.
+ # dir_pn.rmtree
+ # ```
+ #
def mtime() File.mtime(@path) end
@@ -1421,7 +1451,23 @@ def open(...) # :yield: file
File.open(@path, ...)
end
- # See File.readlink. Read symbolic link.
+ # :markup: markdown
+ #
+ # call-seq:
+ # readlink -> new_pathname
+ #
+ # Returns a new pathname containing the string path to the entry referenced by `self`:
+ #
+ # ```ruby
+ # # Create Pathnames.
+ # file_pn = Pathname('doc/extension.rdoc') # => #
+ # target_pn = Pathname('..').join(file_pn) # => #
+ # link_pn = Pathname('lib/u.tmp') # => #
+ # link_pn.make_symlink(target_pn)
+ # link_pn.readlink # => #
+ # link_pn.delete
+ # ```
+ #
def readlink() self.class.new(File.readlink(@path)) end
# See File.rename. Rename the file.
@@ -1457,7 +1503,25 @@ def stat() File.stat(@path) end
#
def lstat() File.lstat(@path) end
- # See File.symlink. Creates a symbolic link.
+ # :markup: markdown
+ #
+ # call-seq:
+ # make_symlink(path) -> 0
+ #
+ # Creates a symbolic link at the path in `self` to the entry at `path`:
+ #
+ # ```ruby
+ # # Create Pathnames.
+ # file_pn = Pathname('doc/extension.rdoc') # => #
+ # target_pn = Pathname('..').join(file_pn) # => #
+ # link_pn = Pathname('lib/u.tmp') # => #
+ # # Create link and verify.
+ # link_pn.make_symlink(target_pn)
+ # file_pn.read == link_pn.read # => true
+ # link_pn.delete # Clean up.
+ # ```
+ #
+ # See also: #read, #readlink, #symlink?.
def make_symlink(old) File.symlink(old, @path) end
# See File.truncate. Truncate the file to +length+ bytes.
@@ -1844,11 +1908,28 @@ def size() FileTest.size(@path) end
# See FileTest.size?.
def size?() FileTest.size?(@path) end
-
# See FileTest.sticky?.
def sticky?() FileTest.sticky?(@path) end
- # See FileTest.symlink?.
+ # :markup: markdown
+ #
+ # call-seq:
+ # symlink? -> true or false
+ #
+ # Returns whether the entry at the path in `self` is a symbolic link:
+ #
+ # ```ruby
+ # # Create Pathnames.
+ # file_pn = Pathname('doc/extension.rdoc') # => #
+ # target_pn = Pathname('..').join(file_pn) # => #
+ # link_pn = Pathname('lib/u.tmp') # => #
+ # link_pn.symlink? # => false
+ # # Create link.
+ # link_pn.make_symlink(target_pn)
+ # link_pn.symlink? # => true
+ # link_pn.delete # Clean up.
+ # ```
+ #
def symlink?() FileTest.symlink?(@path) end
# See FileTest.writable?.
diff --git a/ruby.c b/ruby.c
index b9648bf155a1ad..f33b86419d589c 100644
--- a/ruby.c
+++ b/ruby.c
@@ -2227,7 +2227,7 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result)
const bool read_stdin = (strcmp(opt->script, "-") == 0);
if (read_stdin) {
- pm_options_encoding_set(options, rb_enc_name(rb_locale_encoding()));
+ pm_options_encoding_set(options, rb_enc_name(IF_UTF8_PATH(rb_utf8_encoding(), rb_locale_encoding())));
}
if (opt->src.enc.name != 0) {
pm_options_encoding_set(options, StringValueCStr(opt->src.enc.name));
@@ -2844,7 +2844,7 @@ load_file_internal(VALUE argp_v)
enc = rb_enc_from_index(opt->src.enc.index);
}
else if (f == rb_stdin) {
- enc = rb_locale_encoding();
+ enc = IF_UTF8_PATH(rb_utf8_encoding(), rb_locale_encoding());
}
else {
enc = rb_utf8_encoding();
diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb
index 62d954c2bf7eee..28d94b0515121a 100644
--- a/spec/ruby/core/kernel/require_spec.rb
+++ b/spec/ruby/core/kernel/require_spec.rb
@@ -34,7 +34,7 @@
features -= %w[java.rb jruby/util.rb]
when "ruby"
so = RbConfig::CONFIG['DLEXT']
- features -= ["windows_1252.#{so}", "windows_31.#{so}"]
+ features.reject! { |feature| feature.end_with?("windows_1252.#{so}", "windows_31j.#{so}") }
features.reject! { |feature| feature.end_with? "encdb.#{so}" }
features.reject! { |feature| feature.end_with? "transdb.#{so}" }
features.reject! { |feature| feature.include?('-fake') }