diff --git a/common.mk b/common.mk index 19eeb9a31b..eabf49258d 100644 --- a/common.mk +++ b/common.mk @@ -710,9 +710,10 @@ prelude.$(OBJEXT): {$(VPATH)}prelude.c # dependencies for optional sources. compile.$(OBJEXT): {$(VPATH)}opt_sc.inc {$(VPATH)}optunifs.inc -win32/win32.$(OBJEXT): {$(VPATH)}win32/win32.c {$(VPATH)}dln.h {$(VPATH)}dln_find.c \ +win32/win32.$(OBJEXT): {$(VPATH)}win32/win32.c {$(VPATH)}win32/file.h \ + {$(VPATH)}dln.h {$(VPATH)}dln_find.c \ {$(VPATH)}internal.h {$(VPATH)}util.h $(RUBY_H_INCLUDES) $(PLATFORM_D) -win32/file.$(OBJEXT): {$(VPATH)}win32/file.c {$(VPATH)}thread.h \ +win32/file.$(OBJEXT): {$(VPATH)}win32/file.c {$(VPATH)}win32/file.h {$(VPATH)}thread.h \ $(RUBY_H_INCLUDES) $(PLATFORM_D) $(NEWLINE_C): $(srcdir)/enc/trans/newline.trans $(srcdir)/tool/transcode-tblgen.rb diff --git a/win32/file.c b/win32/file.c index 7de58d48c9..5b5f899935 100644 --- a/win32/file.c +++ b/win32/file.c @@ -9,6 +9,7 @@ #include #include #include +#include "win32/file.h" #ifndef INVALID_FILE_ATTRIBUTES # define INVALID_FILE_ATTRIBUTES ((DWORD)-1) @@ -658,15 +659,16 @@ rb_file_expand_path_internal(VALUE fname, VALUE dname, int abs_mode, int long_na return result; } -ssize_t rb_w32_wreadlink(const WCHAR *path, WCHAR *buf, size_t bufsize); - VALUE rb_readlink(VALUE path) { - ssize_t len; - WCHAR *wpath, wbuf[MAX_PATH]; + DWORD len; + VALUE wtmp = 0, str; + rb_w32_reparse_buffer_t rbuf, *rp = &rbuf; + WCHAR *wpath, *wbuf; rb_encoding *enc; UINT cp, path_cp; + int e; FilePathValue(path); enc = rb_enc_get(path); @@ -678,13 +680,23 @@ rb_readlink(VALUE path) wpath = mbstr_to_wstr(cp, RSTRING_PTR(path), RSTRING_LEN(path)+rb_enc_mbminlen(enc), NULL); if (!wpath) rb_memerror(); - len = rb_w32_wreadlink(wpath, wbuf, numberof(wbuf)); + e = rb_w32_read_reparse_point(wpath, rp, sizeof(rbuf), &wbuf, &len); + if (e == ERROR_MORE_DATA) { + size_t size = rb_w32_reparse_buffer_size(len + 1); + rp = ALLOCV(wtmp, size); + e = rb_w32_read_reparse_point(wpath, rp, size, &wbuf, &len); + } free(wpath); - if (len < 0) rb_sys_fail_path(path); + if (e) { + ALLOCV_END(wtmp); + rb_syserr_fail_path(rb_w32_map_errno(e), path); + } enc = rb_filesystem_encoding(); cp = path_cp = code_page(enc); if (cp == INVALID_CODE_PAGE) cp = CP_UTF8; - return append_wstr(rb_enc_str_new(0, 0, enc), wbuf, len, cp, path_cp, enc); + str = append_wstr(rb_enc_str_new(0, 0, enc), wbuf, len, cp, path_cp, enc); + ALLOCV_END(wtmp); + return str; } static void * diff --git a/win32/file.h b/win32/file.h new file mode 100644 index 0000000000..95a792a63f --- /dev/null +++ b/win32/file.h @@ -0,0 +1,40 @@ +#ifndef RUBY_WIN32_FILE_H +#define RUBY_WIN32_FILE_H + +#define MAX_REPARSE_PATH_LEN 4092 + +enum { + MINIMUM_REPARSE_BUFFER_PATH_LEN = 4 +}; +/* License: Ruby's */ +typedef struct { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[4]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[4]; + } MountPointReparseBuffer; + }; +} rb_w32_reparse_buffer_t; + +#define rb_w32_reparse_buffer_size(n) \ + (sizeof(rb_w32_reparse_buffer_t) + \ + sizeof(WCHAR)*((n)-MINIMUM_REPARSE_BUFFER_PATH_LEN)) + +int rb_w32_read_reparse_point(const WCHAR *path, rb_w32_reparse_buffer_t *rp, + size_t bufsize, WCHAR **result, DWORD *len); + +#endif /* RUBY_WIN32_FILE_H */ diff --git a/win32/win32.c b/win32/win32.c index beea93871e..9b838b516e 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -50,6 +50,7 @@ #endif #include "ruby/win32.h" #include "win32/dir.h" +#include "win32/file.h" #include "internal.h" #define isdirsep(x) ((x) == '/' || (x) == '\\') @@ -4667,33 +4668,9 @@ link(const char *from, const char *to) # define IO_REPARSE_TAG_SYMLINK 0xA000000CL #endif -/* License: Ruby's */ -typedef struct { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[MAXPATHLEN * 2]; - } SymbolicLinkReparseBuffer; - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[MAXPATHLEN * 2]; - } MountPointReparseBuffer; - }; -} reparse_buffer_t; - /* License: Ruby's */ static int -reparse_symlink(const WCHAR *path, reparse_buffer_t *rp) +reparse_symlink(const WCHAR *path, rb_w32_reparse_buffer_t *rp, size_t size) { HANDLE f; DWORD ret; @@ -4721,12 +4698,12 @@ reparse_symlink(const WCHAR *path, reparse_buffer_t *rp) } if (!device_io_control(f, FSCTL_GET_REPARSE_POINT, NULL, 0, - rp, sizeof(*rp), &ret, NULL)) { - e = map_errno(GetLastError()); + rp, size, &ret, NULL)) { + e = GetLastError(); } else if (rp->ReparseTag != IO_REPARSE_TAG_SYMLINK && rp->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) { - e = EINVAL; + e = ERROR_INVALID_PARAMETER; } CloseHandle(f); return e; @@ -4736,68 +4713,86 @@ reparse_symlink(const WCHAR *path, reparse_buffer_t *rp) int rb_w32_reparse_symlink_p(const WCHAR *path) { - reparse_buffer_t rp; - return reparse_symlink(path, &rp) == 0; + rb_w32_reparse_buffer_t rp; + switch (reparse_symlink(path, &rp, sizeof(rp))) { + case 0: + case ERROR_MORE_DATA: + return TRUE; + } + return FALSE; } /* License: Ruby's */ -ssize_t -rb_w32_wreadlink(const WCHAR *path, WCHAR *buf, size_t bufsize) +int +rb_w32_read_reparse_point(const WCHAR *path, rb_w32_reparse_buffer_t *rp, + size_t bufsize, WCHAR **result, DWORD *len) { - reparse_buffer_t rp; - int e = reparse_symlink(path, &rp); + int e = reparse_symlink(path, rp, bufsize); DWORD ret; - if (!e) { + if (!e || e == ERROR_MORE_DATA) { void *name; - if (rp.ReparseTag == IO_REPARSE_TAG_SYMLINK) { - name = ((char *)rp.SymbolicLinkReparseBuffer.PathBuffer + - rp.SymbolicLinkReparseBuffer.PrintNameOffset); - ret = rp.SymbolicLinkReparseBuffer.PrintNameLength; + if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + name = ((char *)rp->SymbolicLinkReparseBuffer.PathBuffer + + rp->SymbolicLinkReparseBuffer.PrintNameOffset); + ret = rp->SymbolicLinkReparseBuffer.PrintNameLength; } else { /* IO_REPARSE_TAG_MOUNT_POINT */ /* +4/-4 means to drop "\??\" */ - name = ((char *)rp.MountPointReparseBuffer.PathBuffer + - rp.MountPointReparseBuffer.SubstituteNameOffset + + name = ((char *)rp->MountPointReparseBuffer.PathBuffer + + rp->MountPointReparseBuffer.SubstituteNameOffset + 4 * sizeof(WCHAR)); - ret = rp.MountPointReparseBuffer.SubstituteNameLength - + ret = rp->MountPointReparseBuffer.SubstituteNameLength - 4 * sizeof(WCHAR); } + *result = name; + *len = ret / sizeof(WCHAR); + if (e) { + if ((char *)name + ret + sizeof(WCHAR) > (char *)rp + bufsize) + return e; + /* SubstituteName is not used */ + } ((WCHAR *)name)[ret/sizeof(WCHAR)] = L'\0'; translate_wchar(name, L'\\', L'/'); - bufsize *= sizeof(WCHAR); - memcpy(buf, name, ret > bufsize ? bufsize : ret); + return 0; } else { - errno = e; - return -1; + return e; } - return ret / sizeof(WCHAR); } /* License: Ruby's */ static ssize_t w32_readlink(UINT cp, const char *path, char *buf, size_t bufsize) { - WCHAR *wpath; - WCHAR wbuf[MAXPATHLEN]; + WCHAR *wpath, *wname; + VALUE wtmp; + size_t size = rb_w32_reparse_buffer_size(bufsize); + rb_w32_reparse_buffer_t *rp = ALLOCV(wtmp, size); + DWORD len; ssize_t ret; + int e; wpath = mbstr_to_wstr(cp, path, -1, NULL); - if (!wpath) return -1; - ret = rb_w32_wreadlink(wpath, wbuf, MAXPATHLEN); + if (!wpath) { + ALLOCV_END(wtmp); + return -1; + } + e = rb_w32_read_reparse_point(wpath, rp, size, &wname, &len); free(wpath); - if (ret < 0) return ret; - ret = WideCharToMultiByte(cp, 0, wbuf, ret, buf, bufsize, NULL, NULL); - if (!ret) { - int e = GetLastError(); - if (e == ERROR_INSUFFICIENT_BUFFER) { - ret = bufsize; - } - else { - errno = map_errno(e); - ret = -1; - } + if (e && e != ERROR_MORE_DATA) { + ALLOCV_END(wtmp); + errno = map_errno(e); + return -1; + } + ret = WideCharToMultiByte(cp, 0, wname, len, buf, bufsize, NULL, NULL); + if (e) { + ret = bufsize; + } + else if (!ret) { + e = GetLastError(); + errno = map_errno(e); + ret = -1; } return ret; }