山本です。

"U.Nakamura" <usa / garbagecollect.jp> wrote:
(2004/07/02 09:56)

>というわけで、とりあえずは
> 「st_devとst_inoを正しく設定しないといけないの?」
>というのが問題で、速度などは二の次かなーと思います。

個人的には、両方とも0でいいような気がします。Windows だと、
ファイルハンドルに対してしか同一性を保証できないようなので・・・

>まあ、Cygwinの実装はそれなりに参考になると思います。

http://www.kaimei.org/note/mag/in_cyg.html でしょうか。
st_ino の取得にはかなり複雑な方法が必要なようですね・・・

>st_devとst_inoを完璧に設定するよりは簡単じゃないかなあ。

ああ、IOのインスタンスメソッドにすれば簡単ですね。
(Unix では st_dev, st_ino を使って、Windows でもファイルが開きっぱなしなので
  GetFileInformationByHandle で問題なし。他の環境では NotImplementedError ?)
最初、stat()のようにパスから比較するのをイメージしていました。

次の点が不完全ですが、とりあえず stat() fstat() を自前で実装してみました。

  - UNC で動くか確かめてない
  - ファイルサイズが32bit(st_size が符号なしなら簡単ですが、そういった保証が得られませんでした)
  - とりあえず st_dev, st_ino は 0
  - IF_SOCK は未実装

Tietew さんのように struct stat を差し替えようと思いましたが、組み込み ruby で
アプリ側の struct stat と異なってもいいのか自信がなかったので、とりあえず 32bit にしました。

# 以前ファイルの属性を得るのに、"FindFirstFile" と、"CreateFile + GetFileSizeなど"
# を比べて、CreateFileは相当重かった記憶があるのですが、なぜか全然遅くなりませんでした。
# やりかたがまずかったのか、もしくはWin98では重いのかもしれません。

Index: win32.h
===================================================================
RCS file: /var/cvs/src/ruby/win32/win32.h,v
retrieving revision 1.51
diff -u -w -b -p -r1.51 win32.h
--- win32.h	19 Feb 2004 09:08:23 -0000	1.51
+++ win32.h	2 Jul 2004 03:42:22 -0000
@@ -113,12 +113,12 @@ extern "C++" {
 #define write(h, b, l)		_write(h, b, l)
 #define _open			_sopen
 #define sopen			_sopen
-#undef fstat
-#define fstat(fd,st)		rb_w32_fstat(fd,st)
 #endif
 #define fsync(h)		_commit(h)
 #undef stat
 #define stat(path,st)		rb_w32_stat(path,st)
+#undef fstat
+#define fstat(fd,st)		rb_w32_fstat(fd,st)
 #undef execv
 #define execv(path,argv)	rb_w32_aspawn(P_OVERLAY,path,argv)
 
Index: win32.c
===================================================================
RCS file: /var/cvs/src/ruby/win32/win32.c,v
retrieving revision 1.117
diff -u -w -b -p -r1.117 win32.c
--- win32.c	21 Jun 2004 00:27:39 -0000	1.117
+++ win32.c	2 Jul 2004 03:41:58 -0000
@@ -2725,23 +2725,80 @@ isUNCRoot(const char *path)
     return 0;
 }
 
-#ifdef __BORLANDC__
-#undef fstat
+static time_t
+filetime_to_unixtime(const FILETIME *ft)
+{
+    FILETIME loc;
+    SYSTEMTIME st;
+    struct tm tm;
+    time_t t;
+
+    if (!FileTimeToLocalFileTime(ft, &loc)) {
+	return 0;
+    }
+    if (!FileTimeToSystemTime(&loc, &st)) {
+	return 0;
+    }
+    memset(&tm, 0, sizeof(tm));
+    tm.tm_year = st.wYear - 1900;
+    tm.tm_mon = st.wMonth - 1;
+    tm.tm_mday = st.wDay;
+    tm.tm_hour = st.wHour;
+    tm.tm_min = st.wMinute;
+    tm.tm_sec = st.wSecond;
+    t = mktime(&tm);
+    return t == -1 ? 0 : t;
+}
+
 int
-rb_w32_fstat(int fd, struct stat *st)
+os_stat(HANDLE h, struct stat *st)
 {
     BY_HANDLE_FILE_INFORMATION info;
-    int ret = fstat(fd, st);
 
-    if (ret) return ret;
-    st->st_mode &= ~(S_IWGRP | S_IWOTH);
-    if (GetFileInformationByHandle((HANDLE)_get_osfhandle(fd), &info) &&
-	!(info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
-	st->st_mode |= S_IWUSR;
+    memset(st, 0, sizeof(struct stat));
+    switch (GetFileType(h)) {
+      case FILE_TYPE_DISK:
+        if (!GetFileInformationByHandle(h, &info)) {
+	    errno = map_errno(GetLastError());
+	    return -1;
+	}
+	st->st_mode = S_IREAD;
+	if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+	    st->st_mode |= (S_IFDIR | S_IWRITE | S_IWUSR | S_IEXEC);
+	else if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+	    st->st_mode |= (S_IFREG);
+	else
+	    st->st_mode |= (S_IFREG | S_IWRITE | S_IWUSR);
+	st->st_nlink = info.nNumberOfLinks;
+//	st->st_dev = st->st_rdev = info.dwVolumeSerialNumber;
+	st->st_atime = filetime_to_unixtime(&info.ftLastAccessTime);
+	st->st_mtime = filetime_to_unixtime(&info.ftLastWriteTime);
+	st->st_ctime = filetime_to_unixtime(&info.ftCreationTime);
+	st->st_size = info.nFileSizeLow;
+//	st->st_size = info.nFileSizeHigh;
+//	st->st_size << 32;
+//	st->st_size += info.nFileSizeLow;
+	break;
+      case FILE_TYPE_CHAR:
+	st->st_mode = S_IFCHR;
+	break;
+      case FILE_TYPE_PIPE:
+	st->st_mode = S_IFIFO;
+	break;
+    }
+    return 0;
     }
-    return ret;
+
+int
+rb_w32_fstat(int fd, struct stat *st)
+{
+    long h = _get_osfhandle(fd);
+
+    if (h < 0)
+	return -1;
+    else
+	return os_stat((HANDLE)h, st);
 }
-#endif
 
 int
 rb_w32_stat(const char *path, struct stat *st)
@@ -2750,6 +2807,7 @@ rb_w32_stat(const char *path, struct sta
     char *buf1, *s, *end;
     int len;
     int ret;
+    HANDLE h;
 
     if (!path || !st) {
 	errno = EFAULT;
@@ -2778,9 +2836,36 @@ rb_w32_stat(const char *path, struct sta
     } else if (*end == '\\' || (buf1 + 1 == end && *end == ':'))
 	strcat(buf1, ".");
 
-    ret = stat(buf1, st);
-    if (ret == 0) {
-	st->st_mode &= ~(S_IWGRP | S_IWOTH);
+    h = CreateFile(
+	buf1,
+	GENERIC_READ,
+	FILE_SHARE_READ | FILE_SHARE_WRITE,
+	NULL,
+	OPEN_EXISTING,
+	FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
+	NULL
+    );
+    if (h == INVALID_HANDLE_VALUE) {
+	errno = map_errno(GetLastError());
+	return -1;
+    }
+    ret = os_stat(h, st);
+    CloseHandle(h);
+
+    if (ret == 0 && S_ISREG(st->st_mode)) {
+	end = buf1 + strlen(buf1);
+	while (buf1 < end) {
+	    end = CharPrev(buf1, end);
+	    if (*end == '.') {
+		if ((strcmpi(end, ".bat") == 0) ||
+		    (strcmpi(end, ".cmd") == 0) ||
+		    (strcmpi(end, ".com") == 0) ||
+		    (strcmpi(end, ".exe") == 0)) {
+		    st->st_mode |= S_IEXEC;
+		}
+		break;
+	    }
+	}
     }
     return ret;
 }