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
5 changes: 5 additions & 0 deletions doc/distribution/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
85 changes: 61 additions & 24 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* ```
*
*/

Expand Down Expand 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 <i>new_name</i> for the existing file
* <i>old_name</i>. 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
Expand All @@ -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
Expand Down Expand Up @@ -6415,18 +6446,24 @@ rb_stat_p(VALUE obj)
}

/*
* :markup: markdown
*
* call-seq:
* stat.symlink? -> true or false
* symlink? -> true or false
*
* Returns <code>true</code> if <i>stat</i> is a symbolic link,
* <code>false</code> 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 <code>false</code> 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)
* ```
*
*/

Expand Down
10 changes: 10 additions & 0 deletions lib/rubygems/util/atomic_file_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
91 changes: 86 additions & 5 deletions pathname_builtin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,37 @@ def birthtime() File.birthtime(@path) end
#
def ctime() File.ctime(@path) end

# See <tt>File.mtime</tt>. 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


Expand Down Expand Up @@ -1421,7 +1451,23 @@ def open(...) # :yield: file
File.open(@path, ...)
end

# See <tt>File.readlink</tt>. 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') # => #<Pathname:doc/extension.rdoc>
# target_pn = Pathname('..').join(file_pn) # => #<Pathname:../doc/extension.rdoc>
# link_pn = Pathname('lib/u.tmp') # => #<Pathname:lib/u.tmp>
# link_pn.make_symlink(target_pn)
# link_pn.readlink # => #<Pathname:../doc/extension.rdoc>
# link_pn.delete
# ```
#
def readlink() self.class.new(File.readlink(@path)) end

# See <tt>File.rename</tt>. Rename the file.
Expand Down Expand Up @@ -1457,7 +1503,25 @@ def stat() File.stat(@path) end
#
def lstat() File.lstat(@path) end

# See <tt>File.symlink</tt>. 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') # => #<Pathname:doc/extension.rdoc>
# target_pn = Pathname('..').join(file_pn) # => #<Pathname:../doc/extension.rdoc>
# link_pn = Pathname('lib/u.tmp') # => #<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 <tt>File.truncate</tt>. Truncate the file to +length+ bytes.
Expand Down Expand Up @@ -1844,11 +1908,28 @@ def size() FileTest.size(@path) end

# See <tt>FileTest.size?</tt>.
def size?() FileTest.size?(@path) end

# See <tt>FileTest.sticky?</tt>.
def sticky?() FileTest.sticky?(@path) end

# See <tt>FileTest.symlink?</tt>.
# :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') # => #<Pathname:doc/extension.rdoc>
# target_pn = Pathname('..').join(file_pn) # => #<Pathname:../doc/extension.rdoc>
# link_pn = Pathname('lib/u.tmp') # => #<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 <tt>FileTest.writable?</tt>.
Expand Down
4 changes: 2 additions & 2 deletions ruby.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion spec/ruby/core/kernel/require_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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') }
Expand Down