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') }