merge revision(s) 34849,34853,34854,34855,34859,34862,35384,35385,36811,36812,36850,36907,36908: [Backport #7174]

* Makefile.in (PLATFORM_DIR): add a variable for `win32` directory.

	* Makefile.in (clean-platform): add new target.
	  It cleans `win32` directory.

	* common.mk (clean): add a dependency for `win32` directory.

	* common.mk (distclean): ditto.

	* common.mk (distclean-platform): add new target.
	  It cleans `win32` directory.

	* common.mk ($(PLATFORM_D)): add new target to make `win32` directory.

	* common.mk (win32/win32.$(OBJEXT)): move win32.o into `win32`
	  directory.

	* common.mk (win32/file.$(OBJEXT)): add new target for win32/file.c.

	* configure.in: move win32.o into `win32` directory and add
	  win32/file.o to MISSING.

	* file.c (file_load_ok, rb_file_load_ok): replace static
	  file_load_ok() with public rb_file_load_ok().
	  It's to link Windows implementation in win32/file.c.

	* file.c (rb_find_file_ext_safe): ditto.

	* file.c (rb_find_file_safe): ditto.

	* win32/file.c (rb_file_load_ok): new file. Add Windows specific
	  optimized implementation of rb_file_load_ok(). We created a
	  separated file to avoid too many #ifdef macro which is unreadable.

	* win32/Makefile.sub (PLATFORM_DIR): add a variable for `win32`
	  directory.

	* win32/Makefile.sub (MISSING): move win32.obj into `win32`
	  directory and add win32/file.obj to MISSING.

	* win32/Makefile.sub (MAKEDIRS): replace MINIRUBY with BASERUBY.
	  It's because miniruby doesn't exist when making `win32` directory.

	* win32/Makefile.sub (clean-platform): add new target to clean `win32`
	  directory.

	* win32/Makefile.sub ({$(srcdir)}.c{}.obj): make it not match
	  win32/file.c to build properly.

	* win32/Makefile.sub (win32/win32.$(OBJEXT)): move win32.obj into
	 `win32` directory.
	  Patch created with Luis Lavena.
	  [ruby-core:42480] [Feature #5999]

	* win32/Makefile.sub (MAKEDIRS): use mkdir of cmd.exe instead of ruby.
	  [Bug #6103] [ruby-core:43012]

	* win32/README.win32: added a notice about command extension of cmd.exe.

	* win32/makedirs.bat: new command to make intermediate
	  directories, and not to report any errors if the directory
	  already exists.

	* win32/Makefile.sub (MAKEDIRS): enable command extensions.

	* win32/file.c (INVALID_FILE_ATTRIBUTES): define for old SDK.

	* configure.in (mingw): add shlwapi to the list of dependency
	  libs for Windows.

	* win32/Makefile.sub (EXTSOLIBS): ditto.

	* internal.h: declare internal functions rb_w32_init_file,
	  rb_file_expand_path_internal and rb_file_expand_path_fast.

	* file.c (Init_File): invoke Windows initialization rb_w32_init_file

	* win32/file.c (rb_file_load_path_internal): new function.
	  Windows-specific implementation that replaces file_expand_path.
	  [Bug #6836][ruby-core:46996]

	* win32/file.c (rb_w32_init_file): new function. Initialize codepage
	  cache for faster conversion encodings lookup.

	* file.c (file_expand_path): rename to rb_file_expand_path_internal.
	  Conditionally exclude from Windows.

	* file.c (rb_file_expand_path_fast): new function. delegates to
	  rb_file_expand_path_internal without performing a hit to the
	  filesystem.

	* file.c (file_expand_path_1): use rb_file_expand_path_internal without
	  path expansion (used by require).

	* file.c (rb_find_file_ext_safe): ditto.

	* file.c (rb_find_file_safe): ditto.

	* load.c (rb_get_expanded_load_path): use rb_file_expand_path_fast.

	* load.c (rb_feature_provided): ditto.

	* file.c (rb_file_expand_path): use rb_file_expand_path_internal with
	  path expansion.

	* file.c (rb_file_absolute_path): ditto.

	* test/ruby/test_file_exhaustive.rb: new tests to exercise
	  rb_file_expand_path_internal implementation and compliance with
	  existing behaviors.

	* test/ruby/test_file_exhaustive.rb: fix test introduced in r36811 for
	  posix environments where HOME is not defined.  [ruby-core:47322]


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_9_3@37321 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
usa 2012-10-25 08:16:34 +00:00
parent 9b1d07629d
commit 2c40007895
14 changed files with 1086 additions and 38 deletions

109
ChangeLog
View file

@ -1,3 +1,112 @@
Thu Oct 25 16:59:17 2012 Luis Lavena <luislavena@gmail.com>
* test/ruby/test_file_exhaustive.rb: fix test introduced in r36811 for
posix environments where HOME is not defined. [ruby-core:47322]
Thu Oct 25 16:59:17 2012 Luis Lavena <luislavena@gmail.com>
* configure.in (mingw): add shlwapi to the list of dependency
libs for Windows.
* win32/Makefile.sub (EXTSOLIBS): ditto.
* internal.h: declare internal functions rb_w32_init_file,
rb_file_expand_path_internal and rb_file_expand_path_fast.
* file.c (Init_File): invoke Windows initialization rb_w32_init_file
* win32/file.c (rb_file_load_path_internal): new function.
Windows-specific implementation that replaces file_expand_path.
[Bug #6836][ruby-core:46996]
* win32/file.c (rb_w32_init_file): new function. Initialize codepage
cache for faster conversion encodings lookup.
* file.c (file_expand_path): rename to rb_file_expand_path_internal.
Conditionally exclude from Windows.
* file.c (rb_file_expand_path_fast): new function. delegates to
rb_file_expand_path_internal without performing a hit to the
filesystem.
* file.c (file_expand_path_1): use rb_file_expand_path_internal without
path expansion (used by require).
* file.c (rb_find_file_ext_safe): ditto.
* file.c (rb_find_file_safe): ditto.
* load.c (rb_get_expanded_load_path): use rb_file_expand_path_fast.
* load.c (rb_feature_provided): ditto.
* file.c (rb_file_expand_path): use rb_file_expand_path_internal with
path expansion.
* file.c (rb_file_absolute_path): ditto.
* test/ruby/test_file_exhaustive.rb: new tests to exercise
rb_file_expand_path_internal implementation and compliance with
existing behaviors.
Thu Oct 25 16:59:17 2012 NAKAMURA Usaku <usa@ruby-lang.org>
* win32/file.c (INVALID_FILE_ATTRIBUTES): define for old SDK.
Thu Oct 25 16:59:17 2012 Nobuyoshi Nakada <nobu@ruby-lang.org>
* win32/makedirs.bat: new command to make intermediate
directories, and not to report any errors if the directory
already exists.
* win32/Makefile.sub (MAKEDIRS): enable command extensions.
Thu Oct 25 16:59:17 2012 NAKAMURA Usaku <usa@ruby-lang.org>
* win32/Makefile.sub (MAKEDIRS): use mkdir of cmd.exe instead of ruby.
[Bug #6103] [ruby-core:43012]
* win32/README.win32: added a notice about command extension of cmd.exe.
Thu Oct 25 16:59:17 2012 Hiroshi Shirosaki <h.shirosaki@gmail.com>
* Makefile.in (PLATFORM_DIR): add a variable for `win32` directory.
* Makefile.in (clean-platform): add new target.
It cleans `win32` directory.
* common.mk (clean): add a dependency for `win32` directory.
* common.mk (distclean): ditto.
* common.mk (distclean-platform): add new target.
It cleans `win32` directory.
* common.mk ($(PLATFORM_D)): add new target to make `win32` directory.
* common.mk (win32/win32.$(OBJEXT)): move win32.o into `win32`
directory.
* common.mk (win32/file.$(OBJEXT)): add new target for win32/file.c.
* configure.in: move win32.o into `win32` directory and add
win32/file.o to MISSING.
* file.c (file_load_ok, rb_file_load_ok): replace static
file_load_ok() with public rb_file_load_ok().
It's to link Windows implementation in win32/file.c.
* file.c (rb_find_file_ext_safe): ditto.
* file.c (rb_find_file_safe): ditto.
* win32/file.c (rb_file_load_ok): new file. Add Windows specific
optimized implementation of rb_file_load_ok(). We created a
separated file to avoid too many #ifdef macro which is unreadable.
* win32/Makefile.sub (PLATFORM_DIR): add a variable for `win32`
directory.
* win32/Makefile.sub (MISSING): move win32.obj into `win32`
directory and add win32/file.obj to MISSING.
* win32/Makefile.sub (MAKEDIRS): replace MINIRUBY with BASERUBY.
It's because miniruby doesn't exist when making `win32` directory.
* win32/Makefile.sub (clean-platform): add new target to clean `win32`
directory.
* win32/Makefile.sub ({$(srcdir)}.c{}.obj): make it not match
win32/file.c to build properly.
* win32/Makefile.sub (win32/win32.$(OBJEXT)): move win32.obj into
`win32` directory.
Patch created with Luis Lavena.
[ruby-core:42480] [Feature #5999]
Fri Oct 19 13:28:29 2012 Nobuyoshi Nakada <nobu@ruby-lang.org>
* configure.in (opt-dir): allow multiple directories separated by

View file

@ -10,6 +10,7 @@ NULL = /dev/null
srcdir = @srcdir@
top_srcdir = $(srcdir)
hdrdir = $(srcdir)/include
PLATFORM_DIR = @PLATFORM_DIR@
CC = @CC@
CPP = @CPP@
@ -342,6 +343,9 @@ clean-capi distclean-capi realclean-capi:
@echo $(@:-capi=ing) capi
$(Q)$(RMALL) $(CAPIOUT)
clean-platform:
@$(RM) $(PLATFORM_D)
-$(Q) $(RMDIR) $(PLATFORM_DIR) 2> /dev/null || true
ext/extinit.$(OBJEXT): ext/extinit.c $(SETUP)
$(ECHO) compiling $@

View file

@ -26,6 +26,7 @@ EXTCONF = extconf.rb
RBCONFIG = ./.rbconfig.time
LIBRUBY_EXTS = ./.libruby-with-ext.time
REVISION_H = ./.revision.time
PLATFORM_D = ./$(PLATFORM_DIR)/.time
RDOCOUT = $(EXTOUT)/rdoc
CAPIOUT = doc/capi
ID_H_TARGET = -id.h-
@ -426,7 +427,7 @@ install-prereq: $(CLEAR_INSTALLED_LIST) PHONY
clear-installed-list: PHONY
@> $(INSTALLED_LIST) set MAKE="$(MAKE)"
clean: clean-ext clean-local clean-enc clean-golf clean-rdoc clean-capi clean-extout
clean: clean-ext clean-local clean-enc clean-golf clean-rdoc clean-capi clean-extout clean-platform
clean-local:: PHONY
@$(RM) $(OBJS) $(MINIOBJS) $(MAINOBJ) $(LIBRUBY_A) $(LIBRUBY_SO) $(LIBRUBY) $(LIBRUBY_ALIASES)
@$(RM) $(PROGRAM) $(WPROGRAM) miniruby$(EXEEXT) dmyext.$(OBJEXT) $(ARCHFILE) .*.time
@ -436,10 +437,11 @@ clean-golf: PHONY
@$(RM) $(GORUBY)$(EXEEXT) $(GOLFOBJS)
clean-rdoc: PHONY
clean-capi: PHONY
clean-platform: PHONY
clean-extout: PHONY
clean-docs: clean-rdoc clean-capi
distclean: distclean-ext distclean-local distclean-enc distclean-golf distclean-extout
distclean: distclean-ext distclean-local distclean-enc distclean-golf distclean-extout distclean-platform
distclean-local:: clean-local
@$(RM) $(MKFILES) yasmdata.rb *.inc
@$(RM) config.cache config.status config.status.lineno $(PRELUDES)
@ -450,6 +452,7 @@ distclean-golf: clean-golf
distclean-rdoc: PHONY
distclean-capi: PHONY
distclean-extout: clean-extout
distclean-platform: clean-platform
realclean:: realclean-ext realclean-local realclean-enc realclean-golf realclean-extout
realclean-local:: distclean-local
@ -573,7 +576,12 @@ dl_os2.$(OBJEXT): {$(VPATH)}dl_os2.c
ia64.$(OBJEXT): {$(VPATH)}ia64.s
$(CC) $(CFLAGS) -c $<
win32.$(OBJEXT): {$(VPATH)}win32.c $(RUBY_H_INCLUDES)
$(PLATFORM_D):
$(Q) $(MAKEDIRS) $(PLATFORM_DIR)
@exit > $@
win32/win32.$(OBJEXT): {$(VPATH)}win32/win32.c $(RUBY_H_INCLUDES) $(PLATFORM_D)
win32/file.$(OBJEXT): {$(VPATH)}win32/file.c $(RUBY_H_INCLUDES) $(PLATFORM_D)
###

View file

@ -1060,7 +1060,7 @@ main()
AC_CHECK_FUNCS(cygwin_conv_path)
AC_LIBOBJ([langinfo])
],
[mingw*], [ LIBS="-lshell32 -lws2_32 -limagehlp $LIBS"
[mingw*], [ LIBS="-lshell32 -lws2_32 -limagehlp -lshlwapi $LIBS"
ac_cv_header_a_out_h=no
ac_cv_header_pwd_h=no
ac_cv_header_utime_h=no
@ -2586,11 +2586,13 @@ AS_CASE(["$target_os"],
fi
EXPORT_PREFIX=' '
DLDFLAGS="${DLDFLAGS}"' $(DEFFILE)'
AC_LIBOBJ([win32])
AC_LIBOBJ([win32/win32])
AC_LIBOBJ([win32/file])
COMMON_LIBS=m
# COMMON_MACROS="WIN32_LEAN_AND_MEAN="
COMMON_HEADERS="winsock2.h windows.h"
THREAD_MODEL=win32
PLATFORM_DIR=win32
])
LIBRUBY_ALIASES=''
FIRSTMAKEFILE=GNUmakefile:cygwin/GNUmakefile.in
@ -2743,6 +2745,7 @@ AC_SUBST(EXPORT_PREFIX)
AC_SUBST(SYMBOL_PREFIX)
AC_SUBST(MINIOBJS)
AC_SUBST(THREAD_MODEL)
AC_SUBST(PLATFORM_DIR)
MAKEFILES="Makefile `echo $FIRSTMAKEFILE | sed 's/:.*//'`"
MAKEFILES="`echo $MAKEFILES`"

51
file.c
View file

@ -2857,8 +2857,9 @@ rb_home_dir(const char *user, VALUE result)
return result;
}
static VALUE
file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result)
#ifndef _WIN32
VALUE
rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_name, VALUE result)
{
const char *s, *b;
char *buf, *p, *pend, *root;
@ -2917,7 +2918,7 @@ file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result)
/* specified drive, but not full path */
int same = 0;
if (!NIL_P(dname) && !not_same_drive(dname, s[0])) {
file_expand_path(dname, Qnil, abs_mode, result);
rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
BUFINIT();
if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) {
/* ok, same drive */
@ -2943,7 +2944,7 @@ file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result)
#endif
else if (!rb_is_absolute_path(s)) {
if (!NIL_P(dname)) {
file_expand_path(dname, Qnil, abs_mode, result);
rb_file_expand_path_internal(dname, Qnil, abs_mode, long_name, result);
BUFINIT();
rb_enc_associate(result, rb_enc_check(result, fname));
}
@ -3173,6 +3174,7 @@ file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result)
ENC_CODERANGE_CLEAR(result);
return result;
}
#endif /* _WIN32 */
#define EXPAND_PATH_BUFFER() rb_usascii_str_new(0, MAXPATHLEN + 2)
@ -3183,14 +3185,21 @@ file_expand_path(VALUE fname, VALUE dname, int abs_mode, VALUE result)
static VALUE
file_expand_path_1(VALUE fname)
{
return file_expand_path(fname, Qnil, 0, EXPAND_PATH_BUFFER());
return rb_file_expand_path_internal(fname, Qnil, 0, 0, EXPAND_PATH_BUFFER());
}
VALUE
rb_file_expand_path(VALUE fname, VALUE dname)
{
check_expand_path_args(fname, dname);
return file_expand_path(fname, dname, 0, EXPAND_PATH_BUFFER());
return rb_file_expand_path_internal(fname, dname, 0, 1, EXPAND_PATH_BUFFER());
}
VALUE
rb_file_expand_path_fast(VALUE fname, VALUE dname)
{
check_expand_path_args(fname, dname);
return rb_file_expand_path_internal(fname, dname, 0, 0, EXPAND_PATH_BUFFER());
}
/*
@ -3227,7 +3236,7 @@ VALUE
rb_file_absolute_path(VALUE fname, VALUE dname)
{
check_expand_path_args(fname, dname);
return file_expand_path(fname, dname, 1, EXPAND_PATH_BUFFER());
return rb_file_expand_path_internal(fname, dname, 1, 1, EXPAND_PATH_BUFFER());
}
/*
@ -5055,8 +5064,9 @@ rb_path_check(const char *path)
return 1;
}
static int
file_load_ok(const char *path)
#ifndef _WIN32
int
rb_file_load_ok(const char *path)
{
int ret = 1;
int fd = open(path, O_RDONLY);
@ -5073,12 +5083,7 @@ file_load_ok(const char *path)
(void)close(fd);
return ret;
}
int
rb_file_load_ok(const char *path)
{
return file_load_ok(path);
}
#endif
static int
is_explicit_relative(const char *path)
@ -5130,7 +5135,7 @@ rb_find_file_ext_safe(VALUE *filep, const char *const *ext, int safe_level)
fnlen = RSTRING_LEN(fname);
for (i=0; ext[i]; i++) {
rb_str_cat2(fname, ext[i]);
if (file_load_ok(RSTRING_PTR(fname))) {
if (rb_file_load_ok(RSTRING_PTR(fname))) {
*filep = copy_path_class(fname, *filep);
return (int)(i+1);
}
@ -5158,8 +5163,8 @@ rb_find_file_ext_safe(VALUE *filep, const char *const *ext, int safe_level)
RB_GC_GUARD(str) = rb_get_path_check(str, safe_level);
if (RSTRING_LEN(str) == 0) continue;
file_expand_path(fname, str, 0, tmp);
if (file_load_ok(RSTRING_PTR(tmp))) {
rb_file_expand_path_internal(fname, str, 0, 0, tmp);
if (rb_file_load_ok(RSTRING_PTR(tmp))) {
*filep = copy_path_class(tmp, *filep);
return (int)(j+1);
}
@ -5198,7 +5203,7 @@ rb_find_file_safe(VALUE path, int safe_level)
if (safe_level >= 1 && !fpath_check(path)) {
rb_raise(rb_eSecurityError, "loading from unsafe path %s", f);
}
if (!file_load_ok(f)) return 0;
if (!rb_file_load_ok(f)) return 0;
if (!expanded)
path = copy_path_class(file_expand_path_1(path), path);
return path;
@ -5218,9 +5223,9 @@ rb_find_file_safe(VALUE path, int safe_level)
VALUE str = RARRAY_PTR(load_path)[i];
RB_GC_GUARD(str) = rb_get_path_check(str, safe_level);
if (RSTRING_LEN(str) > 0) {
file_expand_path(path, str, 0, tmp);
rb_file_expand_path_internal(path, str, 0, 0, tmp);
f = RSTRING_PTR(tmp);
if (file_load_ok(f)) goto found;
if (rb_file_load_ok(f)) goto found;
}
}
return 0;
@ -5453,4 +5458,8 @@ Init_File(void)
rb_define_method(rb_cStat, "setuid?", rb_stat_suid, 0);
rb_define_method(rb_cStat, "setgid?", rb_stat_sgid, 0);
rb_define_method(rb_cStat, "sticky?", rb_stat_sticky, 0);
#ifdef _WIN32
rb_w32_init_file();
#endif
}

View file

@ -92,8 +92,15 @@ void rb_call_end_proc(VALUE data);
/* file.c */
VALUE rb_home_dir(const char *user, VALUE result);
VALUE rb_realpath_internal(VALUE basedir, VALUE path, int strict);
VALUE rb_file_expand_path_fast(VALUE, VALUE);
VALUE rb_file_expand_path_internal(VALUE, VALUE, int, int, VALUE);
void Init_File(void);
#ifdef _WIN32
/* file.c, win32/file.c */
void rb_w32_init_file(void);
#endif
/* gc.c */
void Init_heap(void);

4
load.c
View file

@ -43,7 +43,7 @@ rb_get_expanded_load_path(void)
ary = rb_ary_new2(RARRAY_LEN(load_path));
for (i = 0; i < RARRAY_LEN(load_path); ++i) {
VALUE path = rb_file_expand_path(RARRAY_PTR(load_path)[i], Qnil);
VALUE path = rb_file_expand_path_fast(RARRAY_PTR(load_path)[i], Qnil);
rb_str_freeze(path);
rb_ary_push(ary, path);
}
@ -233,7 +233,7 @@ rb_feature_provided(const char *feature, const char **loading)
if (*feature == '.' &&
(feature[1] == '/' || strncmp(feature+1, "./", 2) == 0)) {
fullpath = rb_file_expand_path(rb_str_new2(feature), Qnil);
fullpath = rb_file_expand_path_fast(rb_str_new2(feature), Qnil);
feature = RSTRING_PTR(fullpath);
}
if (ext && !strchr(ext, '/')) {

View file

@ -14,6 +14,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def setup
@dir = Dir.mktmpdir("rubytest-file")
@rootdir = "#{DRIVE}/"
File.chown(-1, Process.gid, @dir)
@file = make_tmp_filename("file")
@zerofile = make_tmp_filename("zerofile")
@ -409,13 +410,62 @@ class TestFileExhaustive < Test::Unit::TestCase
else
assert_equal("/foo", File.expand_path('/foo'))
end
end
def test_expand_path_encoding
drive = (DRIVE ? 'C:' : '')
if Encoding.find("filesystem") == Encoding::CP1251
a = "#{drive}/\u3042\u3044\u3046\u3048\u304a".encode("cp932")
else
a = "#{drive}/\u043f\u0440\u0438\u0432\u0435\u0442".encode("cp1251")
end
assert_equal(a, File.expand_path(a))
a = "#{drive}/\225\\\\"
if File::ALT_SEPARATOR == '\\'
[%W"cp437 #{drive}/\225", %W"cp932 #{drive}/\225\\"]
else
[["cp437", a], ["cp932", a]]
end.each do |cp, expected|
assert_equal(expected.force_encoding(cp), File.expand_path(a.dup.force_encoding(cp)), cp)
end
path = "\u3042\u3044\u3046\u3048\u304a".encode("EUC-JP")
assert_equal("#{Dir.pwd}/#{path}".encode("CP932"), File.expand_path(path).encode("CP932"))
path = "\u3042\u3044\u3046\u3048\u304a".encode("CP51932")
assert_equal("#{Dir.pwd}/#{path}", File.expand_path(path))
assert_incompatible_encoding {|d| File.expand_path(d)}
end
def test_expand_path_encoding_filesystem
home = ENV["HOME"]
ENV["HOME"] = "#{DRIVE}/UserHome"
path = "~".encode("US-ASCII")
dir = "C:/".encode("IBM437")
fs = Encoding.find("filesystem")
assert_equal fs, File.expand_path(path).encoding
assert_equal fs, File.expand_path(path, dir).encoding
ensure
ENV["HOME"] = home
end
def test_expand_path_home
assert_kind_of(String, File.expand_path("~")) if ENV["HOME"]
assert_raise(ArgumentError) { File.expand_path("~foo_bar_baz_unknown_user_wahaha") }
assert_raise(ArgumentError) { File.expand_path("~foo_bar_baz_unknown_user_wahaha", "/") }
begin
bug3630 = '[ruby-core:31537]'
home = ENV["HOME"]
home_drive = ENV["HOMEDRIVE"]
home_path = ENV["HOMEPATH"]
user_profile = ENV["USERPROFILE"]
ENV["HOME"] = nil
ENV["HOMEDRIVE"] = nil
ENV["HOMEPATH"] = nil
ENV["USERPROFILE"] = nil
assert_raise(ArgumentError) { File.expand_path("~") }
ENV["HOME"] = "~"
assert_raise(ArgumentError, bug3630) { File.expand_path("~") }
@ -423,10 +473,194 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_raise(ArgumentError, bug3630) { File.expand_path("~") }
ensure
ENV["HOME"] = home
ENV["HOMEDRIVE"] = home_drive
ENV["HOMEPATH"] = home_path
ENV["USERPROFILE"] = user_profile
end
assert_incompatible_encoding {|d| File.expand_path(d)}
end
def test_expand_path_remove_trailing_alternative_data
assert_equal File.join(@rootdir, "aaa"), File.expand_path("#{@rootdir}/aaa::$DATA")
assert_equal File.join(@rootdir, "aa:a"), File.expand_path("#{@rootdir}/aa:a:$DATA")
assert_equal File.join(@rootdir, "aaa:$DATA"), File.expand_path("#{@rootdir}/aaa:$DATA")
end if DRIVE
def test_expand_path_resolve_empty_string_current_directory
assert_equal(Dir.pwd, File.expand_path(""))
end
def test_expand_path_resolve_dot_current_directory
assert_equal(Dir.pwd, File.expand_path("."))
end
def test_expand_path_resolve_file_name_relative_current_directory
assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo"))
end
def test_ignore_nil_dir_string
assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo", nil))
end
def test_expand_path_resolve_file_name_and_dir_string_relative
assert_equal(File.join(Dir.pwd, "bar", "foo"),
File.expand_path("foo", "bar"))
end
def test_expand_path_cleanup_dots_file_name
bug = "[ruby-talk:18512]"
assert_equal(File.join(Dir.pwd, ".a"), File.expand_path(".a"), bug)
assert_equal(File.join(Dir.pwd, "..a"), File.expand_path("..a"), bug)
if DRIVE
# cleanup dots only on Windows
assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a."), bug)
skip "FIXME"
assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a.."), bug)
else
assert_equal(File.join(Dir.pwd, "a."), File.expand_path("a."), bug)
assert_equal(File.join(Dir.pwd, "a.."), File.expand_path("a.."), bug)
end
end
def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_a_complete_path
assert_equal(@dir, File.expand_path("", "#{@dir}"))
assert_equal(File.join(@dir, "a"), File.expand_path("a", "#{@dir}"))
assert_equal(File.join(@dir, "a"), File.expand_path("../a", "#{@dir}/xxx"))
assert_equal(@rootdir, File.expand_path(".", "#{@rootdir}"))
end
def test_expand_path_ignores_supplied_dir_if_path_contains_a_drive_letter
assert_equal(@rootdir, File.expand_path(@rootdir, "D:/"))
end if DRIVE
def test_expand_path_removes_trailing_slashes_from_absolute_path
assert_equal(File.join(@rootdir, "foo"), File.expand_path("#{@rootdir}foo/"))
assert_equal(File.join(@rootdir, "foo.rb"), File.expand_path("#{@rootdir}foo.rb/"))
end
def test_expand_path_removes_trailing_spaces_from_absolute_path
assert_equal(File.join(@rootdir, "a"), File.expand_path("#{@rootdir}a "))
end if DRIVE
def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_dir_s_drive
assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar"))
end if DRIVE
def test_expand_path_converts_a_pathname_which_starts_with_a_slash_and_unc_pathname
assert_equal("//foo", File.expand_path('//foo', "//bar"))
assert_equal("//bar/foo", File.expand_path('/foo', "//bar"))
assert_equal("//foo", File.expand_path('//foo', "/bar"))
end if DRIVE
def test_expand_path_converts_a_dot_with_unc_dir
assert_equal("//", File.expand_path('.', "//"))
end
def test_expand_path_preserves_unc_path_root
assert_equal("//", File.expand_path("//"))
assert_equal("//", File.expand_path("//."))
assert_equal("//", File.expand_path("//.."))
end
def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_host_share
assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar"))
end if DRIVE
def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_a_current_drive
assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo'))
end
def test_expand_path_returns_tainted_strings_or_not
assert_equal(true, File.expand_path('foo').tainted?)
assert_equal(true, File.expand_path('foo'.taint).tainted?)
assert_equal(true, File.expand_path('/foo'.taint).tainted?)
assert_equal(true, File.expand_path('foo', 'bar').tainted?)
assert_equal(true, File.expand_path('foo', '/bar'.taint).tainted?)
assert_equal(true, File.expand_path('foo'.taint, '/bar').tainted?)
assert_equal(true, File.expand_path('~').tainted?) if ENV["HOME"]
if DRIVE
assert_equal(true, File.expand_path('/foo').tainted?)
assert_equal(false, File.expand_path('//foo').tainted?)
assert_equal(true, File.expand_path('C:/foo'.taint).tainted?)
assert_equal(false, File.expand_path('C:/foo').tainted?)
assert_equal(true, File.expand_path('foo', '/bar').tainted?)
assert_equal(true, File.expand_path('foo', 'C:/bar'.taint).tainted?)
assert_equal(true, File.expand_path('foo'.taint, 'C:/bar').tainted?)
assert_equal(false, File.expand_path('foo', 'C:/bar').tainted?)
assert_equal(false, File.expand_path('C:/foo/../bar').tainted?)
assert_equal(false, File.expand_path('foo', '//bar').tainted?)
else
assert_equal(false, File.expand_path('/foo').tainted?)
assert_equal(false, File.expand_path('foo', '/bar').tainted?)
end
end
def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_home_as_base
old_home = ENV["HOME"]
home = ENV["HOME"] = "#{DRIVE}/UserHome"
assert_equal(home, File.expand_path("~"))
assert_equal(home, File.expand_path("~", "C:/FooBar"))
assert_equal(File.join(home, "a"), File.expand_path("~/a", "C:/FooBar"))
ensure
ENV["HOME"] = old_home
end
def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_unc_home
old_home = ENV["HOME"]
unc_home = ENV["HOME"] = "//UserHome"
assert_equal(unc_home, File.expand_path("~"))
ensure
ENV["HOME"] = old_home
end if DRIVE
def test_expand_path_does_not_modify_a_home_string_argument
old_home = ENV["HOME"]
home = ENV["HOME"] = "#{DRIVE}/UserHome"
str = "~/a"
assert_equal("#{home}/a", File.expand_path(str))
assert_equal("~/a", str)
ensure
ENV["HOME"] = old_home
end
def test_expand_path_raises_argument_error_for_any_supplied_username
bug = '[ruby-core:39597]'
assert_raise(ArgumentError, bug) { File.expand_path("~anything") }
end if DRIVE
def test_expand_path_raises_a_type_error_if_not_passed_a_string_type
assert_raise(TypeError) { File.expand_path(1) }
assert_raise(TypeError) { File.expand_path(nil) }
assert_raise(TypeError) { File.expand_path(true) }
end
def test_expand_path_expands_dot_dir
assert_equal("#{DRIVE}/dir", File.expand_path("#{DRIVE}/./dir"))
end
def test_expand_path_does_not_modify_the_string_argument
str = "./a/b/../c"
assert_equal("#{Dir.pwd}/a/c", File.expand_path(str, Dir.pwd))
assert_equal("./a/b/../c", str)
end
def test_expand_path_returns_a_string_when_passed_a_string_subclass
sub = Class.new(String)
str = sub.new "./a/b/../c"
path = File.expand_path(str, Dir.pwd)
assert_equal("#{Dir.pwd}/a/c", path)
assert_instance_of(String, path)
end
def test_expand_path_accepts_objects_that_have_a_to_path_method
klass = Class.new { def to_path; "a/b/c"; end }
obj = klass.new
assert_equal("#{Dir.pwd}/a/b/c", File.expand_path(obj))
end
def test_basename
assert_equal(File.basename(@file).sub(/\.test$/, ""), File.basename(@file, ".test"))
assert_equal("", s = File.basename(""))

View file

@ -1,10 +1,10 @@
#define RUBY_VERSION "1.9.3"
#define RUBY_PATCHLEVEL 297
#define RUBY_PATCHLEVEL 298
#define RUBY_RELEASE_DATE "2012-10-19"
#define RUBY_RELEASE_DATE "2012-10-25"
#define RUBY_RELEASE_YEAR 2012
#define RUBY_RELEASE_MONTH 10
#define RUBY_RELEASE_DAY 19
#define RUBY_RELEASE_DAY 25
#include "ruby/version.h"

View file

@ -74,6 +74,7 @@ RM = $(COMSPEC) /C $(srcdir:/=\)\win32\rm.bat
RMDIR = $(COMSPEC) /C $(srcdir:/=\)\win32\rmdirs.bat
RMDIRS = $(COMSPEC) /C $(srcdir:/=\)\win32\rmdirs.bat
RMALL = $(COMSPEC) /C $(srcdir:/=\)\win32\rmall.bat
MAKEDIRS = $(COMSPEC) /E:ON /C $(srcdir:/=\)\win32\makedirs.bat
CP = copy > nul
MV = move > nul
!if !defined(BASERUBY)
@ -122,6 +123,9 @@ PLATFORM = mswin32
!ifdef NTVER
ARCHDEFS = -D_WIN32_WINNT=$(NTVER) $(ARCHDEFS)
!endif
!if !defined(PLATFORM_DIR)
PLATFORM_DIR = win32
!endif
arch = $(ARCH)-$(PLATFORM)
sitearch = $(ARCH)-$(RT)
@ -219,13 +223,13 @@ RFLAGS = -r
EXTLIBS =
!endif
!if !defined(LIBS)
LIBS = oldnames.lib user32.lib advapi32.lib shell32.lib ws2_32.lib imagehlp.lib $(EXTLIBS)
LIBS = oldnames.lib user32.lib advapi32.lib shell32.lib ws2_32.lib imagehlp.lib shlwapi.lib $(EXTLIBS)
!endif
!if "$(ENABLE_WIN95)" == "yes"
LIBS = unicows.lib $(LIBS)
!endif
!if !defined(MISSING)
MISSING = acosh.obj cbrt.obj crypt.obj erf.obj ffs.obj langinfo.obj lgamma_r.obj strlcat.obj strlcpy.obj tgamma.obj win32.obj setproctitle.obj
MISSING = acosh.obj cbrt.obj crypt.obj erf.obj ffs.obj langinfo.obj lgamma_r.obj strlcat.obj strlcpy.obj tgamma.obj win32/win32.obj win32/file.obj setproctitle.obj
!endif
ARFLAGS = -machine:$(MACHINE) -out:
@ -286,8 +290,6 @@ DEFAULT_PRELUDES = $(NO_GEM_PRELUDE)
DEFAULT_PRELUDES = $(YES_GEM_PRELUDE)
!endif
MAKEDIRS = $(MINIRUBY) -run -e mkdir -- -p
!if !defined(STACK)
!if "$(ARCH)" == "x64" || "$(ARCH)" == "ia64"
STACK = 0x400000
@ -920,6 +922,10 @@ clean-rdoc distclean-rdoc realclean-rdoc:
clean-capi distclean-capi realclean-capi:
-$(Q)$(RMALL) $(CAPIOUT:/=\)
clean-platform:
$(Q)$(RM) $(PLATFORM_D:/=\)
-$(Q)$(RMDIR) $(PLATFORM_DIR:/=\)
clean-ext distclean-ext realclean-ext::
!if "$(EXTS)" != ""
@for %I in ($(EXTS)) \
@ -993,7 +999,7 @@ $(ruby_pc): $(RBCONFIG)
{$(win_srcdir)/sys}.c.obj:
$(ECHO) compiling $(<:\=/)
$(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c -Tc$(<:\=/)
{$(srcdir)}.c.obj:
{$(srcdir)}.c{}.obj:
$(ECHO) compiling $(<:\=/)
$(Q) $(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -c -Tc$(<:\=/)
.c.obj:
@ -1040,7 +1046,7 @@ enc/unicode/name2ctype.h: {$(srcdir)}enc/unicode/name2ctype.h.blt
$(OBJS): {$(hdrdir)/ruby}win32.h
dir.$(OBJEXT) win32.$(OBJEXT): {$(srcdir)}win32/dir.h
dir.$(OBJEXT) win32/win32.$(OBJEXT): {$(srcdir)}win32/dir.h
ext/extinit.obj: ext/extinit.c $(SETUP)
$(ECHO) compiling ext/extinit.c

View file

@ -25,7 +25,11 @@
* sed
* ruby 1.8
(5) If you want built binaries to run on Windows 95 series (including
(5) Enable Command Extension of your command line. It's the default behavior
of cmd.exe. If you want to enable it explicitly, run cmd.exe with /E:ON
option.
(6) If you want built binaries to run on Windows 95 series (including
98 and Me), you need unicows.lib at compile time, and unicows.dll
at run time.

661
win32/file.c Normal file
View file

@ -0,0 +1,661 @@
#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include <winbase.h>
#include <wchar.h>
#include <shlwapi.h>
#ifndef INVALID_FILE_ATTRIBUTES
# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
/* cache 'encoding name' => 'code page' into a hash */
static VALUE rb_code_page;
#define IS_DIR_SEPARATOR_P(c) (c == L'\\' || c == L'/')
#define IS_DIR_UNC_P(c) (IS_DIR_SEPARATOR_P(c[0]) && IS_DIR_SEPARATOR_P(c[1]))
/* MultiByteToWideChar() doesn't work with code page 51932 */
#define INVALID_CODE_PAGE 51932
#define PATH_BUFFER_SIZE MAX_PATH * 2
#define insecure_obj_p(obj, level) ((level) >= 4 || ((level) > 0 && OBJ_TAINTED(obj)))
static inline void
replace_wchar(wchar_t *s, int find, int replace)
{
while (*s != 0) {
if (*s == find)
*s = replace;
s++;
}
}
/* Convert str from multibyte char to wchar with specified code page */
static inline void
convert_mb_to_wchar(VALUE str, wchar_t **wstr, wchar_t **wstr_pos, size_t *wstr_len, UINT code_page)
{
size_t len;
if (NIL_P(str))
return;
len = MultiByteToWideChar(code_page, 0, RSTRING_PTR(str), -1, NULL, 0) + 1;
*wstr = (wchar_t *)xmalloc(len * sizeof(wchar_t));
if (wstr_pos)
*wstr_pos = *wstr;
MultiByteToWideChar(code_page, 0, RSTRING_PTR(str), -1, *wstr, len);
*wstr_len = len - 2;
}
static inline void
convert_wchar_to_mb(const wchar_t *wstr, char **str, size_t *str_len, UINT code_page)
{
size_t len;
len = WideCharToMultiByte(code_page, 0, wstr, -1, NULL, 0, NULL, NULL);
*str = (char *)xmalloc(len * sizeof(char));
WideCharToMultiByte(code_page, 0, wstr, -1, *str, len, NULL, NULL);
/* do not count terminator as part of the string length */
*str_len = len - 1;
}
/*
Return user's home directory using environment variables combinations.
Memory allocated by this function should be manually freed afterwards.
Try:
HOME, HOMEDRIVE + HOMEPATH and USERPROFILE environment variables
TODO: Special Folders - Profile and Personal
*/
static wchar_t *
home_dir(void)
{
wchar_t *buffer = NULL;
size_t buffer_len = 0, len = 0;
size_t home_env = 0;
/*
GetEnvironmentVariableW when used with NULL will return the required
buffer size and its terminating character.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188(v=vs.85).aspx
*/
if (len = GetEnvironmentVariableW(L"HOME", NULL, 0)) {
buffer_len = len;
home_env = 1;
}
else if (len = GetEnvironmentVariableW(L"HOMEDRIVE", NULL, 0)) {
buffer_len = len;
if (len = GetEnvironmentVariableW(L"HOMEPATH", NULL, 0)) {
buffer_len += len;
home_env = 2;
}
else {
buffer_len = 0;
}
}
else if (len = GetEnvironmentVariableW(L"USERPROFILE", NULL, 0)) {
buffer_len = len;
home_env = 3;
}
/* allocate buffer */
if (home_env)
buffer = (wchar_t *)xmalloc(buffer_len * sizeof(wchar_t));
switch (home_env) {
case 1:
/* HOME */
GetEnvironmentVariableW(L"HOME", buffer, buffer_len);
break;
case 2:
/* HOMEDRIVE + HOMEPATH */
len = GetEnvironmentVariableW(L"HOMEDRIVE", buffer, buffer_len);
GetEnvironmentVariableW(L"HOMEPATH", buffer + len, buffer_len - len);
break;
case 3:
/* USERPROFILE */
GetEnvironmentVariableW(L"USERPROFILE", buffer, buffer_len);
break;
default:
break;
}
if (home_env) {
/* sanitize backslashes with forwardslashes */
replace_wchar(buffer, L'\\', L'/');
return buffer;
}
return NULL;
}
/* Remove trailing invalid ':$DATA' of the path. */
static inline size_t
remove_invalid_alternative_data(wchar_t *wfullpath, size_t size)
{
static const wchar_t prime[] = L":$DATA";
enum { prime_len = (sizeof(prime) / sizeof(wchar_t)) -1 };
if (size <= prime_len || _wcsnicmp(wfullpath + size - prime_len, prime, prime_len) != 0)
return size;
/* alias of stream */
/* get rid of a bug of x64 VC++ */
if (wfullpath[size - (prime_len + 1)] == ':') {
/* remove trailing '::$DATA' */
size -= prime_len + 1; /* prime */
wfullpath[size] = L'\0';
}
else {
/* remove trailing ':$DATA' of paths like '/aa:a:$DATA' */
wchar_t *pos = wfullpath + size - (prime_len + 1);
while (!IS_DIR_SEPARATOR_P(*pos) && pos != wfullpath) {
if (*pos == L':') {
size -= prime_len; /* alternative */
wfullpath[size] = L'\0';
break;
}
pos--;
}
}
return size;
}
/* Return system code page. */
static inline UINT
system_code_page(void)
{
return AreFileApisANSI() ? CP_ACP : CP_OEMCP;
}
/*
Return code page number of the encoding.
Cache code page into a hash for performance since finding the code page in
Encoding#names is slow.
*/
static UINT
code_page(rb_encoding *enc)
{
VALUE code_page_value, name_key;
VALUE encoding, names_ary = Qundef, name;
char *enc_name;
struct RString fake_str;
ID names;
long i;
if (!enc)
return system_code_page();
enc_name = (char *)rb_enc_name(enc);
fake_str.basic.flags = T_STRING|RSTRING_NOEMBED;
fake_str.basic.klass = rb_cString;
fake_str.as.heap.len = strlen(enc_name);
fake_str.as.heap.ptr = enc_name;
fake_str.as.heap.aux.capa = fake_str.as.heap.len;
name_key = (VALUE)&fake_str;
ENCODING_CODERANGE_SET(name_key, rb_usascii_encindex(), ENC_CODERANGE_7BIT);
code_page_value = rb_hash_lookup(rb_code_page, name_key);
if (code_page_value != Qnil)
return (UINT)FIX2INT(code_page_value);
name_key = rb_usascii_str_new2(enc_name);
encoding = rb_enc_from_encoding(enc);
if (!NIL_P(encoding)) {
CONST_ID(names, "names");
names_ary = rb_funcall(encoding, names, 0);
}
/* map US-ASCII and ASCII-8bit as code page 20127 (us-ascii) */
if (enc == rb_usascii_encoding() || enc == rb_ascii8bit_encoding()) {
UINT code_page = 20127;
rb_hash_aset(rb_code_page, name_key, INT2FIX(code_page));
return code_page;
}
if (names_ary != Qundef) {
for (i = 0; i < RARRAY_LEN(names_ary); i++) {
name = RARRAY_PTR(names_ary)[i];
if (strncmp("CP", RSTRING_PTR(name), 2) == 0) {
int code_page = atoi(RSTRING_PTR(name) + 2);
if (code_page != 0) {
rb_hash_aset(rb_code_page, name_key, INT2FIX(code_page));
return (UINT)code_page;
}
}
}
}
rb_hash_aset(rb_code_page, name_key, INT2FIX(INVALID_CODE_PAGE));
return INVALID_CODE_PAGE;
}
static inline VALUE
fix_string_encoding(VALUE str, rb_encoding *encoding)
{
VALUE result, tmp;
tmp = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), encoding);
result = rb_str_encode(tmp, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil);
return result;
}
/*
Replace the last part of the path to long name.
We try to avoid to call FindFirstFileW() since it takes long time.
*/
static inline size_t
replace_to_long_name(wchar_t **wfullpath, size_t size, int heap)
{
WIN32_FIND_DATAW find_data;
HANDLE find_handle;
/*
Skip long name conversion if the path is already long name.
Short name is 8.3 format.
http://en.wikipedia.org/wiki/8.3_filename
This check can be skipped for directory components that have file
extensions longer than 3 characters, or total lengths longer than
12 characters.
http://msdn.microsoft.com/en-us/library/windows/desktop/aa364980(v=vs.85).aspx
*/
size_t const max_short_name_size = 8 + 1 + 3;
size_t const max_extension_size = 3;
size_t path_len = 1, extension_len = 0;
wchar_t *pos = *wfullpath;
if (size == 3 && pos[1] == L':' && pos[2] == L'\\' && pos[3] == L'\0') {
/* root path doesn't need short name expansion */
return size;
}
pos = *wfullpath + size - 1;
while (!IS_DIR_SEPARATOR_P(*pos) && pos != *wfullpath) {
if (!extension_len && *pos == L'.') {
extension_len = path_len - 1;
}
if (path_len > max_short_name_size || extension_len > max_extension_size) {
return size;
}
path_len++;
pos--;
}
find_handle = FindFirstFileW(*wfullpath, &find_data);
if (find_handle != INVALID_HANDLE_VALUE) {
size_t trail_pos = wcslen(*wfullpath);
size_t file_len = wcslen(find_data.cFileName);
FindClose(find_handle);
while (trail_pos > 0) {
if (IS_DIR_SEPARATOR_P((*wfullpath)[trail_pos]))
break;
trail_pos--;
}
size = trail_pos + 1 + file_len;
if ((size + 1) > sizeof(*wfullpath) / sizeof((*wfullpath)[0])) {
wchar_t *buf = (wchar_t *)xmalloc((size + 1) * sizeof(wchar_t));
wcsncpy(buf, *wfullpath, trail_pos + 1);
if (heap)
xfree(*wfullpath);
*wfullpath = buf;
}
wcsncpy(*wfullpath + trail_pos + 1, find_data.cFileName, file_len + 1);
}
return size;
}
VALUE
rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_name, VALUE result)
{
size_t size = 0, wpath_len = 0, wdir_len = 0, whome_len = 0;
size_t buffer_len = 0;
char *fullpath = NULL;
wchar_t *wfullpath = NULL, *wpath = NULL, *wpath_pos = NULL, *wdir = NULL;
wchar_t *whome = NULL, *buffer = NULL, *buffer_pos = NULL;
UINT path_cp, cp;
VALUE path = fname, dir = dname;
wchar_t wfullpath_buffer[PATH_BUFFER_SIZE];
wchar_t path_drive = L'\0', dir_drive = L'\0';
int ignore_dir = 0;
rb_encoding *path_encoding;
int tainted = 0;
/* tainted if path is tainted */
tainted = OBJ_TAINTED(path);
/* get path encoding */
if (NIL_P(dir)) {
path_encoding = rb_enc_get(path);
}
else {
path_encoding = rb_enc_check(path, dir);
}
cp = path_cp = code_page(path_encoding);
/* workaround invalid codepage */
if (path_cp == INVALID_CODE_PAGE) {
cp = CP_UTF8;
if (!NIL_P(path)) {
path = fix_string_encoding(path, path_encoding);
}
}
/* convert char * to wchar_t */
convert_mb_to_wchar(path, &wpath, &wpath_pos, &wpath_len, cp);
/* determine if we need the user's home directory */
/* expand '~' only if NOT rb_file_absolute_path() where `abs_mode` is 1 */
if (abs_mode == 0 && wpath_len > 0 && wpath_pos[0] == L'~' &&
(wpath_len == 1 || IS_DIR_SEPARATOR_P(wpath_pos[1]))) {
/* tainted if expanding '~' */
tainted = 1;
whome = home_dir();
if (whome == NULL) {
xfree(wpath);
rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding `~'");
}
whome_len = wcslen(whome);
if (PathIsRelativeW(whome) && !(whome_len >= 2 && IS_DIR_UNC_P(whome))) {
xfree(wpath);
rb_raise(rb_eArgError, "non-absolute home");
}
/* use filesystem encoding if expanding home dir */
path_encoding = rb_filesystem_encoding();
cp = path_cp = system_code_page();
/* ignores dir since we are expading home */
ignore_dir = 1;
/* exclude ~ from the result */
wpath_pos++;
wpath_len--;
/* exclude separator if present */
if (wpath_len && IS_DIR_SEPARATOR_P(wpath_pos[0])) {
wpath_pos++;
wpath_len--;
}
}
else if (wpath_len >= 2 && wpath_pos[1] == L':') {
if (wpath_len >= 3 && IS_DIR_SEPARATOR_P(wpath_pos[2])) {
/* ignore dir since path contains a drive letter and a root slash */
ignore_dir = 1;
}
else {
/* determine if we ignore dir or not later */
path_drive = wpath_pos[0];
}
}
else if (abs_mode == 0 && wpath_len >= 2 && wpath_pos[0] == L'~') {
wchar_t *wuser = wpath_pos + 1;
wchar_t *pos = wuser;
char *user;
/* tainted if expanding '~' */
tainted = 1;
while (!IS_DIR_SEPARATOR_P(*pos) && *pos != '\0')
pos++;
*pos = '\0';
convert_wchar_to_mb(wuser, &user, &size, cp);
/* convert to VALUE and set the path encoding */
if (path_cp == INVALID_CODE_PAGE) {
VALUE tmp = rb_enc_str_new(user, size, rb_utf8_encoding());
result = rb_str_encode(tmp, rb_enc_from_encoding(path_encoding), 0, Qnil);
rb_str_resize(tmp, 0);
}
else {
result = rb_enc_str_new(user, size, path_encoding);
}
xfree(wpath);
if (user)
xfree(user);
rb_raise(rb_eArgError, "can't find user %s", StringValuePtr(result));
}
/* convert dir */
if (!ignore_dir && !NIL_P(dir)) {
/* fix string encoding */
if (path_cp == INVALID_CODE_PAGE) {
dir = fix_string_encoding(dir, path_encoding);
}
/* convert char * to wchar_t */
convert_mb_to_wchar(dir, &wdir, NULL, &wdir_len, cp);
if (wdir_len >= 2 && wdir[1] == L':') {
dir_drive = wdir[0];
if (wpath_len && IS_DIR_SEPARATOR_P(wpath_pos[0])) {
wdir_len = 2;
}
}
else if (wdir_len >= 2 && IS_DIR_UNC_P(wdir)) {
/* UNC path */
if (wpath_len && IS_DIR_SEPARATOR_P(wpath_pos[0])) {
/* cut the UNC path tail to '//host/share' */
size_t separators = 0;
size_t pos = 2;
while (pos < wdir_len && separators < 2) {
if (IS_DIR_SEPARATOR_P(wdir[pos])) {
separators++;
}
pos++;
}
if (separators == 2)
wdir_len = pos - 1;
}
}
}
/* determine if we ignore dir or not */
if (!ignore_dir && path_drive && dir_drive) {
if (towupper(path_drive) == towupper(dir_drive)) {
/* exclude path drive letter to use dir */
wpath_pos += 2;
wpath_len -= 2;
}
else {
/* ignore dir since path drive is different from dir drive */
ignore_dir = 1;
wdir_len = 0;
}
}
if (!ignore_dir && wpath_len >= 2 && IS_DIR_UNC_P(wpath)) {
/* ignore dir since path has UNC root */
ignore_dir = 1;
wdir_len = 0;
}
else if (!ignore_dir && wpath_len >= 1 && IS_DIR_SEPARATOR_P(wpath[0]) &&
!dir_drive && !(wdir_len >= 2 && IS_DIR_UNC_P(wdir))) {
/* ignore dir since path has root slash and dir doesn't have drive or UNC root */
ignore_dir = 1;
wdir_len = 0;
}
buffer_len = wpath_len + 1 + wdir_len + 1 + whome_len + 1;
buffer = buffer_pos = (wchar_t *)xmalloc((buffer_len + 1) * sizeof(wchar_t));
/* add home */
if (whome_len) {
wcsncpy(buffer_pos, whome, whome_len);
buffer_pos += whome_len;
}
/* Add separator if required */
if (whome_len && wcsrchr(L"\\/:", buffer_pos[-1]) == NULL) {
buffer_pos[0] = L'\\';
buffer_pos++;
}
if (wdir_len) {
/* tainted if dir is used and dir is tainted */
if (!tainted && OBJ_TAINTED(dir))
tainted = 1;
wcsncpy(buffer_pos, wdir, wdir_len);
buffer_pos += wdir_len;
}
/* add separator if required */
if (wdir_len && wcsrchr(L"\\/:", buffer_pos[-1]) == NULL) {
buffer_pos[0] = L'\\';
buffer_pos++;
}
/* now deal with path */
if (wpath_len) {
wcsncpy(buffer_pos, wpath_pos, wpath_len);
buffer_pos += wpath_len;
}
/* GetFullPathNameW requires at least "." to determine current directory */
if (wpath_len == 0) {
buffer_pos[0] = L'.';
buffer_pos++;
}
/* Ensure buffer is NULL terminated */
buffer_pos[0] = L'\0';
/* tainted if path is relative */
if (!tainted && PathIsRelativeW(buffer) && !(buffer_len >= 2 && IS_DIR_UNC_P(buffer)))
tainted = 1;
/* FIXME: Make this more robust */
/* Determine require buffer size */
size = GetFullPathNameW(buffer, PATH_BUFFER_SIZE, wfullpath_buffer, NULL);
if (size > PATH_BUFFER_SIZE) {
/* allocate more memory than alloted originally by PATH_BUFFER_SIZE */
wfullpath = (wchar_t *)xmalloc(size * sizeof(wchar_t));
size = GetFullPathNameW(buffer, size, wfullpath, NULL);
}
else {
wfullpath = wfullpath_buffer;
}
/* Remove any trailing slashes */
if (IS_DIR_SEPARATOR_P(wfullpath[size - 1]) &&
wfullpath[size - 2] != L':' &&
!(size == 2 && IS_DIR_UNC_P(wfullpath))) {
size -= 1;
wfullpath[size] = L'\0';
}
/* Remove any trailing dot */
if (wfullpath[size - 1] == L'.') {
size -= 1;
wfullpath[size] = L'\0';
}
/* removes trailing invalid ':$DATA' */
size = remove_invalid_alternative_data(wfullpath, size);
/* Replace the trailing path to long name */
if (long_name)
size = replace_to_long_name(&wfullpath, size, (wfullpath != wfullpath_buffer));
/* sanitize backslashes with forwardslashes */
replace_wchar(wfullpath, L'\\', L'/');
/* convert to char * */
size = WideCharToMultiByte(cp, 0, wfullpath, size, NULL, 0, NULL, NULL);
if (size > (size_t)RSTRING_LEN(result)) {
rb_str_modify(result);
rb_str_resize(result, size);
}
WideCharToMultiByte(cp, 0, wfullpath, size, RSTRING_PTR(result), size, NULL, NULL);
rb_str_set_len(result, size);
/* convert to VALUE and set the path encoding */
if (path_cp == INVALID_CODE_PAGE) {
VALUE tmp;
size_t len;
rb_enc_associate(result, rb_utf8_encoding());
ENC_CODERANGE_CLEAR(result);
tmp = rb_str_encode(result, rb_enc_from_encoding(path_encoding), 0, Qnil);
len = RSTRING_LEN(tmp);
rb_str_modify(result);
rb_str_resize(result, len);
memcpy(RSTRING_PTR(result), RSTRING_PTR(tmp), len);
rb_str_resize(tmp, 0);
}
rb_enc_associate(result, path_encoding);
ENC_CODERANGE_CLEAR(result);
/* makes the result object tainted if expanding tainted strings or returning modified path */
if (tainted)
OBJ_TAINT(result);
/* TODO: better cleanup */
if (buffer)
xfree(buffer);
if (wpath)
xfree(wpath);
if (wdir)
xfree(wdir);
if (whome)
xfree(whome);
if (wfullpath && wfullpath != wfullpath_buffer)
xfree(wfullpath);
if (fullpath)
xfree(fullpath);
return result;
}
int
rb_file_load_ok(const char *path)
{
int ret = 1;
DWORD attr = GetFileAttributes(path);
if (attr == INVALID_FILE_ATTRIBUTES ||
attr & FILE_ATTRIBUTE_DIRECTORY) {
ret = 0;
}
else {
HANDLE h = CreateFile(path, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
}
else {
ret = 0;
}
}
return ret;
}
void
rb_w32_init_file(void)
{
rb_code_page = rb_hash_new();
/* prevent GC removing rb_code_page */
rb_gc_register_mark_object(rb_code_page);
}

3
win32/makedirs.bat Executable file
View file

@ -0,0 +1,3 @@
@echo off
setlocal EnableExtensions
for %%I in (%*) do if not exist "%%~I/." mkdir "%%~I"

0
win32/rmall.bat Normal file → Executable file
View file