Created
October 19, 2012 14:36
-
-
Save shirosaki/3918535 to your computer and use it in GitHub Desktop.
Backport #7174
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/ChangeLog b/ChangeLog | |
index 7f90628..49391a4 100644 | |
--- a/ChangeLog | |
+++ b/ChangeLog | |
@@ -1,3 +1,112 @@ | |
+Thu Aug 30 07:45:12 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] | |
+ | |
+Fri Aug 24 12:40:15 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. | |
+ | |
+Wed Apr 18 13:59:40 2012 NAKAMURA Usaku <usa@ruby-lang.org> | |
+ | |
+ * win32/file.c (INVALID_FILE_ATTRIBUTES): define for old SDK. | |
+ | |
+Thu Mar 1 15:40:05 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. | |
+ | |
+Wed Feb 29 16:11:34 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. | |
+ | |
+Tue Feb 28 23:20:01 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 | |
diff --git a/Makefile.in b/Makefile.in | |
index b0f4434..df521ca 100644 | |
--- a/Makefile.in | |
+++ b/Makefile.in | |
@@ -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 $@ | |
diff --git a/common.mk b/common.mk | |
index eb89a2b..ca74385 100644 | |
--- a/common.mk | |
+++ b/common.mk | |
@@ -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) | |
### | |
diff --git a/configure.in b/configure.in | |
index 0130e23..9a724ab 100644 | |
--- a/configure.in | |
+++ b/configure.in | |
@@ -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`" | |
diff --git a/file.c b/file.c | |
index 3036285..7070d89 100644 | |
--- a/file.c | |
+++ b/file.c | |
@@ -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 | |
} | |
diff --git a/internal.h b/internal.h | |
index 5d0cff0..59c9284 100644 | |
--- a/internal.h | |
+++ b/internal.h | |
@@ -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); | |
diff --git a/load.c b/load.c | |
index 72ca0eb..163ec4c 100644 | |
--- a/load.c | |
+++ b/load.c | |
@@ -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, '/')) { | |
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb | |
index f89e251..86b2e1a 100644 | |
--- a/test/ruby/test_file_exhaustive.rb | |
+++ b/test/ruby/test_file_exhaustive.rb | |
@@ -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 if DRIVE | |
+ | |
+ 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 | |
+ 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 | |
assert_incompatible_encoding {|d| File.expand_path(d)} | |
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("")) | |
diff --git a/win32/Makefile.sub b/win32/Makefile.sub | |
index 3733dc8..ce354ca 100644 | |
--- a/win32/Makefile.sub | |
+++ b/win32/Makefile.sub | |
@@ -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 | |
diff --git a/win32/README.win32 b/win32/README.win32 | |
index 0737f1b..f6a9a71 100644 | |
--- a/win32/README.win32 | |
+++ b/win32/README.win32 | |
@@ -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. | |
diff --git a/win32/file.c b/win32/file.c | |
new file mode 100644 | |
index 0000000..e3b50b4 | |
--- /dev/null | |
+++ b/win32/file.c | |
@@ -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); | |
+} | |
diff --git a/win32/makedirs.bat b/win32/makedirs.bat | |
new file mode 100755 | |
index 0000000..13740d8 | |
--- /dev/null | |
+++ b/win32/makedirs.bat | |
@@ -0,0 +1,3 @@ | |
+@echo off | |
+setlocal EnableExtensions | |
+for %%I in (%*) do if not exist "%%~I/." mkdir "%%~I" | |
diff --git a/win32/rmall.bat b/win32/rmall.bat | |
old mode 100644 | |
new mode 100755 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment