Index: src/org/jruby/ext/posix/WindowsLibC.java =================================================================== --- src/org/jruby/ext/posix/WindowsLibC.java (revision 79b6c76b222f0e9b06f4b8d8d02f59006a483602) +++ src/org/jruby/ext/posix/WindowsLibC.java (revision ) @@ -1,14 +1,39 @@ package org.jruby.ext.posix; +import com.kenai.jaffl.Pointer; import com.kenai.jaffl.annotations.In; import com.kenai.jaffl.annotations.StdCall; public interface WindowsLibC extends LibC { public int _open_osfhandle(int handle, int flags); - public int _utime64(String filename, UTimBuf64 times); + public int _wmkdir(@In byte[] path); + @StdCall public int GetFileType(int handle); + @StdCall - public boolean CreateHardLinkA(String oldname, String newName, @In byte[] reserved); + public boolean CreateHardLinkW(byte[] oldname, byte[] newName, @In byte[] reserved); + + @StdCall + int CreateFileW( + byte[] lpFileName, + int dwDesiredAccess, + int dwShareMode, + Pointer lpSecurityAttributes, + int dwCreationDisposition, + int dwFlagsAndAttributes, + int hTemplateFile + ); + + @StdCall + boolean SetFileTime( + int hFile, + FileTime lpCreationTime, + FileTime lpLastAccessTime, + FileTime lpLastWriteTime + ); + + @StdCall + boolean CloseHandle(int handle); } Index: src/org/jruby/ext/posix/FileTime.java =================================================================== --- src/org/jruby/ext/posix/FileTime.java (revision ) +++ src/org/jruby/ext/posix/FileTime.java (revision ) @@ -0,0 +1,8 @@ +package org.jruby.ext.posix; + +import com.kenai.jaffl.struct.Struct; + +public class FileTime extends Struct { + public final Unsigned32 dwLowDateTime = new Unsigned32(); + public final Unsigned32 dwHighDateTime = new Unsigned32(); +} Index: test/org/jruby/ext/posix/FileTest.java =================================================================== --- test/org/jruby/ext/posix/FileTest.java (revision ) +++ test/org/jruby/ext/posix/FileTest.java (revision ) @@ -0,0 +1,59 @@ +package org.jruby.ext.posix; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class FileTest { + private static POSIX posix; + + @BeforeClass + public static void setUpClass() throws Exception { + posix = POSIXFactory.getPOSIX(new DummyPOSIXHandler(), true); + } + + @Test + public void utimeTest() throws Throwable { + File f = File.createTempFile("utime", null); + long oldTime = posix.stat(f.getAbsolutePath()).mtime(); + Thread.sleep(2000); + int rval = posix.utimes(f.getAbsolutePath(), null, null); + assertEquals("utime did not return 0", 0, rval); + long newTime = posix.stat(f.getAbsolutePath()).mtime(); + f.delete(); + assertTrue("mtime failed", newTime > oldTime); + } + + @Test + public void linkTest() throws Throwable { + File f1 = File.createTempFile("utime", null); + File f2 = new File(f1.getAbsolutePath() + "link"); + int rval = posix.link(f1.getAbsolutePath(), f2.getAbsolutePath()); + assertEquals("link did not return 0", 0, rval); + assertTrue("Link was not created", f2.exists()); + f1.delete(); + f2.delete(); + } + + @Test + public void mkdirRelativeTest() throws Throwable { + File dir = new File("tmp"); + int rval = posix.mkdir(dir.getPath(), 0); + assertEquals("mkdir did not return 0", 0, rval); + assertTrue("Directory was not created", dir.exists()); + dir.delete(); + } + + @Test + public void mkdirAbsoluteTest() throws Throwable { + File dir = new File("tmp"); + int rval = posix.mkdir(dir.getAbsolutePath(), 0); + assertEquals("mkdir did not return 0", 0, rval); + assertTrue("Directory was not created", dir.exists()); + dir.delete(); + } +} Index: src/org/jruby/ext/posix/WindowsPOSIX.java =================================================================== --- src/org/jruby/ext/posix/WindowsPOSIX.java (revision 79b6c76b222f0e9b06f4b8d8d02f59006a483602) +++ src/org/jruby/ext/posix/WindowsPOSIX.java (revision ) @@ -4,12 +4,15 @@ import static com.kenai.constantine.platform.Errno.*; import static com.kenai.constantine.platform.windows.LastError.*; +import java.io.File; import java.io.FileDescriptor; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; final class WindowsPOSIX extends BaseNativePOSIX { + private static final Charset UTF16_LE = Charset.forName("UTF-16LE"); + private final static int FILE_TYPE_CHAR = 0x0002; private final static Map errorToErrnoMapper @@ -250,15 +253,81 @@ return null; } + private static final int INVALID_HANDLE_VALUE = -1; + + private static final int GENERIC_ALL = 0x10000000; + private static final int GENERIC_READ = 0x80000000; + private static final int GENERIC_WRITE = 0x40000000; + private static final int GENERIC_EXECUTE = 0x2000000; + + private static final int FILE_SHARE_DELETE = 0x00000004; + private static final int FILE_SHARE_READ = 0x00000001; + private static final int FILE_SHARE_WRITE = 0x00000002; + + private static final int CREATE_ALWAYS = 2; + private static final int CREATE_NEW = 1; + private static final int OPEN_ALWAYS = 4; + private static final int OPEN_EXISTING = 3; + private static final int TRUNCATE_EXISTING = 5; + + public static final int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + @Override public int utimes(String path, long[] atimeval, long[] mtimeval) { - UTimBuf64 times = null; - if (atimeval != null && mtimeval != null) { - times = new UTimBuf64(atimeval[0], mtimeval[0]); + WindowsLibC libc = (WindowsLibC) libc(); + + byte[] wpath = toWPath(path); + + FileTime aTime = null; + if (atimeval != null) { + aTime = unixTimeToFileTime(atimeval[0]); } - return ((WindowsLibC)libc())._utime64(path, times); + + FileTime mTime = null; + if (mtimeval != null) { + mTime = unixTimeToFileTime(mtimeval[0]); - } + } + if (aTime == null || mTime == null) { + FileTime nowFile = unixTimeToFileTime(System.currentTimeMillis() / 1000L); + if (aTime == null) aTime = nowFile; + if (mTime == null) mTime = nowFile; + } + + int handle = libc.CreateFileW( + wpath, + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + null, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + 0 + ); + + if (handle == INVALID_HANDLE_VALUE) { + // TODO proper error handling + return -1; + } + + boolean timeSet = libc.SetFileTime(handle, null, aTime, mTime); + libc.CloseHandle(handle); + + return timeSet ? 0 : -1; + } + + private FileTime unixTimeToFileTime(long unixTime) { + // FILETIME is a 64-bit unsigned integer representing + // the number of 100-nanosecond intervals since January 1, 1601 + // UNIX timestamp is number of seconds since January 1, 1970 + // 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days + long ft = (unixTime + 11644473600L) * 10000000L; + + FileTime fileTime = new FileTime(); + fileTime.dwLowDateTime.set(ft & 0xFFFFFFFFL); + fileTime.dwHighDateTime.set((ft >> 32) & 0xFFFFFFFFL); + return fileTime; + } + @Override public int wait(int[] status) { handler.unimplementedError("wait"); @@ -343,25 +412,22 @@ @Override public int mkdir(String path, int mode) { // TODO: somehow handle the mode - try { - byte[] widePath = appendWcharNul(path.getBytes("UTF-16LE")); + byte[] widePath = toWPath(path); - int res = ((WindowsLibC)libc())._wmkdir(widePath); - if (res < 0) { - int error = errno(); - handler.error(mapErrorToErrno(error), path); - } - return res; + int res = ((WindowsLibC)libc())._wmkdir(widePath); + if (res < 0) { + int error = errno(); + handler.error(mapErrorToErrno(error), path); + } + return res; - } catch (UnsupportedEncodingException e) { - // should not really happen - handler.error(Errno.EINVAL, path); - return -1; - } + } - } @Override public int link(String oldpath, String newpath) { - boolean linkCreated = ((WindowsLibC)libc()).CreateHardLinkA(newpath, oldpath, null); + byte[] oldWPath = toWPath(oldpath); + byte[] newWPath = toWPath(newpath); + boolean linkCreated = ((WindowsLibC)libc()).CreateHardLinkW(newWPath, oldWPath, null); + if (!linkCreated) { int error = errno(); handler.error(mapErrorToErrno(error), oldpath + " or " + newpath); @@ -379,11 +445,17 @@ return errno; } - private static byte[] appendWcharNul(byte[] bytes) { - byte[] withNul = new byte[bytes.length + 2]; - System.arraycopy(bytes, 0, withNul, 0, bytes.length); - withNul[bytes.length] = 0; - withNul[bytes.length + 1] = 0; - return withNul; + private static byte[] toWPath(String path) { + boolean absolute = new File(path).isAbsolute(); + if (absolute) { + path = "\\\\?\\" + path; - } + } + + return toWString(path); -} + } + + private static byte[] toWString(String string) { + string += (char)0; + return string.getBytes(UTF16_LE); + } +}