Skip to content

Commit

Permalink
Merge pull request #22 from edwinf/useJavatoOpenFile
Browse files Browse the repository at this point in the history
Use java.nio to open files with file_share_delete permissions
  • Loading branch information
jordansissel committed Jan 9, 2014
2 parents 693fc9c + d00f2ef commit e0dfb20
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 4 deletions.
Binary file added java/JRubyFileExtension.jar
Binary file not shown.
50 changes: 50 additions & 0 deletions java/RubyFileExt.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Created with IntelliJ IDEA.
* User: efrey
* Date: 6/11/13
* Time: 11:00 AM
* To change this template use File | Settings | File Templates.
*
* http://bugs.sun.com/view_bug.do?bug_id=6357433
*
*
*/

import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.util.io.ChannelDescriptor;
import org.jruby.util.io.ChannelStream;
import org.jruby.util.io.InvalidValueException;
import org.jruby.util.io.Stream;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.file.spi.FileSystemProvider;
import java.util.Iterator;

@JRubyClass(name="FileExt", parent="File", include="FileTest")
public class RubyFileExt extends RubyObject {

public RubyFileExt(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}

public RubyFileExt(RubyClass metaClass) {
super(metaClass);
}

@JRubyMethod
public static RubyIO getRubyFile(String path) throws IOException, InvalidValueException{
Path p = FileSystems.getDefault().getPath(path);
OpenOption[] options = new OpenOption[1];
options[0] = StandardOpenOption.READ;
Channel channel = FileChannel.open(p, options);
return new RubyIO(Ruby.getGlobalRuntime(), channel);
}
}
26 changes: 23 additions & 3 deletions lib/filewatch/tail.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
require "filewatch/buftok"
require "filewatch/watch"
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
require "filewatch/winhelper"
end
require "logger"
require "rbconfig"

include Java if defined? JRUBY_VERSION
require "JRubyFileExtension.jar" if defined? JRUBY_VERSION

module FileWatch
class Tail
Expand All @@ -14,6 +21,8 @@ class NoSinceDBPathGiven < StandardError; end

public
def initialize(opts={})
@iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)

if opts[:logger]
@logger = opts[:logger]
else
Expand Down Expand Up @@ -97,7 +106,11 @@ def subscribe(&block)
def _open_file(path, event)
@logger.debug("_open_file: #{path}: opening")
begin
@files[path] = File.open(path)
if @iswindows && defined? JRUBY_VERSION
@files[path] = Java::RubyFileExt::getRubyFile(path)
else
@files[path] = File.open(path)
end
rescue
# don't emit this message too often. if a file that we can't
# read is changing a lot, we'll try to open it more often,
Expand All @@ -114,7 +127,14 @@ def _open_file(path, event)
end

stat = File::Stat.new(path)
inode = [stat.ino, stat.dev_major, stat.dev_minor]

if @iswindows
fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
inode = [fileId, stat.dev_major, stat.dev_minor]
else
inode = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
end

@statcache[path] = inode

if @sincedb.member?(inode)
Expand Down Expand Up @@ -195,7 +215,7 @@ def _sincedb_open
@logger.debug("_sincedb_open: reading from #{path}")
db.each do |line|
ino, dev_major, dev_minor, pos = line.split(" ", 4)
inode = [ino.to_i, dev_major.to_i, dev_minor.to_i]
inode = [ino, dev_major.to_i, dev_minor.to_i]
@logger.debug("_sincedb_open: setting #{inode.inspect} to #{pos.to_i}")
@sincedb[inode] = pos.to_i
end
Expand Down
20 changes: 19 additions & 1 deletion lib/filewatch/watch.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
require "logger"
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
require "filewatch/winhelper"
end

module FileWatch
class Watch
attr_accessor :logger

public
def initialize(opts={})
@iswindows = ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) != nil)
if opts[:logger]
@logger = opts[:logger]
else
Expand Down Expand Up @@ -68,7 +72,13 @@ def each(&block)
next
end

inode = [stat.ino, stat.dev_major, stat.dev_minor]
if @iswindows
fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
inode = [fileId, stat.dev_major, stat.dev_minor]
else
inode = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
end

if inode != @files[path][:inode]
@logger.debug("#{path}: old inode was #{@files[path][:inode].inspect}, new is #{inode.inspect}")
yield(:delete, path)
Expand Down Expand Up @@ -142,6 +152,14 @@ def _discover_file(path, initial=false)
:inode => [stat.ino, stat.dev_major, stat.dev_minor],
:create_sent => false,
}

if @iswindows
fileId = Winhelper.GetWindowsUniqueFileIdentifier(path)
@files[file][:inode] = [fileId, stat.dev_major, stat.dev_minor]
else
@files[file][:inode] = [stat.ino.to_s, stat.dev_major, stat.dev_minor]
end

if initial
@files[file][:initial] = true
end
Expand Down
70 changes: 70 additions & 0 deletions lib/filewatch/winhelper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require "ffi"

module Winhelper
extend FFI::Library

ffi_lib 'kernel32'
ffi_convention :stdcall
class FileTime < FFI::Struct
layout :lowDateTime, :uint,
:highDateTime, :uint
end

#http://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx
class FileInformation < FFI::Struct
def initialize()
createTime = FileTime.new
lastAccessTime = FileTime.new
lastWriteTime = FileTime.new
end

layout :fileAttributes, :uint, #DWORD dwFileAttributes;
:createTime, FileTime, #FILETIME ftCreationTime;
:lastAccessTime, FileTime, #FILETIME ftLastAccessTime;
:lastWriteTime, FileTime, #FILETIME ftLastWriteTime;
:volumeSerialNumber, :uint, #DWORD dwVolumeSerialNumber;
:fileSizeHigh, :uint, #DWORD nFileSizeHigh;
:fileSizeLow, :uint, #DWORD nFileSizeLow;
:numberOfLinks, :uint, #DWORD nNumberOfLinks;
:fileIndexHigh, :uint, #DWORD nFileIndexHigh;
:fileIndexLow, :uint #DWORD nFileIndexLow;
end


#http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
#HANDLE WINAPI CreateFile(_In_ LPCTSTR lpFileName,_In_ DWORD dwDesiredAccess,_In_ DWORD dwShareMode,
# _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,_In_ DWORD dwCreationDisposition,
# _In_ DWORD dwFlagsAndAttributes,_In_opt_ HANDLE hTemplateFile);
attach_function :GetOpenFileHandle, :CreateFileA, [:pointer, :uint, :uint, :pointer, :uint, :uint, :pointer], :pointer

#http://msdn.microsoft.com/en-us/library/windows/desktop/aa364952(v=vs.85).aspx
#BOOL WINAPI GetFileInformationByHandle(_In_ HANDLE hFile,_Out_ LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
attach_function :GetFileInformationByHandle, [:pointer, :pointer], :int

attach_function :CloseHandle, [:pointer], :int


def self.GetWindowsUniqueFileIdentifier(path)
handle = GetOpenFileHandle(path, 0, 7, nil, 3, 128, nil)
fileInfo = Winhelper::FileInformation.new
success = GetFileInformationByHandle(handle, fileInfo)
CloseHandle(handle)
if success == 1
#args = [
# fileInfo[:fileAttributes], fileInfo[:volumeSerialNumber], fileInfo[:fileSizeHigh], fileInfo[:fileSizeLow],
# fileInfo[:numberOfLinks], fileInfo[:fileIndexHigh], fileInfo[:fileIndexLow]
# ]
#p "Information: %u %u %u %u %u %u %u " % args
#this is only guaranteed on NTFS, for ReFS on windows 2012, GetFileInformationByHandleEx should be used with FILE_ID_INFO, which returns a 128 bit identifier
return "#{fileInfo[:volumeSerialNumber]}-#{fileInfo[:fileIndexLow]}-#{fileInfo[:fileIndexHigh]}"
else
#p "cannot retrieve file information, returning path"
return path;
end
end
end

#fileId = Winhelper.GetWindowsUniqueFileIdentifier('C:\inetpub\logs\LogFiles\W3SVC1\u_ex1fdsadfsadfasdf30612.log')
#p "FileId: " + fileId
#p "outside function, sleeping"
#sleep(10)

0 comments on commit e0dfb20

Please sign in to comment.