Index: src/org/jruby/Ruby.java =================================================================== --- src/org/jruby/Ruby.java (revision 8301) +++ src/org/jruby/Ruby.java (working copy) @@ -2621,8 +2621,12 @@ return newRaiseException(getErrno().fastGetClass("EAGAIN"), message); } + public RaiseException newErrnoEISDirError(String message) { + return newRaiseException(getErrno().fastGetClass("EISDIR"), message); + } + public RaiseException newErrnoEISDirError() { - return newRaiseException(getErrno().fastGetClass("EISDIR"), "Is a directory"); + return newErrnoEISDirError("Is a directory"); } public RaiseException newErrnoESPIPEError() { @@ -2865,17 +2869,27 @@ } } - public void registerDescriptor(ChannelDescriptor descriptor) { + public void registerDescriptor(ChannelDescriptor descriptor, boolean isRetained) { cleanDescriptors(); int fileno = descriptor.getFileno(); - descriptors.put(fileno, new WeakDescriptorReference(descriptor, descriptorQueue)); + Integer filenoKey = new Integer(fileno); + descriptors.put(filenoKey, new WeakDescriptorReference(descriptor, descriptorQueue)); + if (isRetained) { + retainedDescriptors.put(filenoKey, descriptor); + } + } + + public void registerDescriptor(ChannelDescriptor descriptor) { + registerDescriptor(descriptor,false); // default: don't retain } public void unregisterDescriptor(int aFileno) { cleanDescriptors(); - descriptors.remove(new Integer(aFileno)); + Integer aFilenoKey = new Integer(aFileno); + descriptors.remove(aFilenoKey); + retainedDescriptors.remove(aFilenoKey); } public ChannelDescriptor getDescriptorByFileno(int aFileno) { @@ -3033,6 +3047,9 @@ private final RubySymbol.SymbolTable symbolTable = new RubySymbol.SymbolTable(this); private Map descriptors = new ConcurrentHashMap(); private ReferenceQueue descriptorQueue = new ReferenceQueue(); + // ChannelDescriptors opened by sysopen are cached to avoid collection + private Map retainedDescriptors = new ConcurrentHashMap(); + private long randomSeed = 0; private long randomSeedSequence = 0; private Random random = new Random(); Index: src/org/jruby/RubyIO.java =================================================================== --- src/org/jruby/RubyIO.java (revision 8301) +++ src/org/jruby/RubyIO.java (working copy) @@ -38,6 +38,7 @@ import java.io.EOFException; import java.io.FileDescriptor; import java.io.IOException; +import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.Reference; @@ -79,6 +80,7 @@ import org.jruby.util.io.InvalidValueException; import org.jruby.util.io.PipeException; import org.jruby.util.io.FileExistsException; +import org.jruby.util.io.DirectoryAsFileException; import org.jruby.util.io.STDIO; import org.jruby.util.io.OpenFile; import org.jruby.util.io.ChannelDescriptor; @@ -95,10 +97,16 @@ protected OpenFile openFile; protected List blockingThreads; + + public void registerDescriptor(ChannelDescriptor descriptor, boolean isRetained) { + getRuntime().registerDescriptor(descriptor,isRetained); + } + public void registerDescriptor(ChannelDescriptor descriptor) { - getRuntime().registerDescriptor(descriptor); + registerDescriptor(descriptor,false); // default: don't retain } + public void unregisterDescriptor(int aFileno) { getRuntime().unregisterDescriptor(aFileno); } @@ -894,6 +902,52 @@ return io; } + @JRubyMethod(name = "sysopen", required = 1, optional = 2, frame = true, meta = true) + public static IRubyObject sysopen(IRubyObject recv, IRubyObject[] args, Block block) { + Ruby runtime = recv.getRuntime(); + + IRubyObject pathString = args[0].convertToString(); + runtime.checkSafeString(pathString); + String path = pathString.toString(); + + ModeFlags modes = null; + int perms = -1; // -1 == don't set permissions + try { + if (args.length > 1) { + IRubyObject modeString = args[1].convertToString(); + modes = getIOModes(runtime, modeString.toString()); + } else { + modes = getIOModes(runtime, "r"); + } + if (args.length > 2) { + RubyInteger permsInt = + args.length >= 3 ? args[2].convertToInteger() : null; + perms = RubyNumeric.fix2int(permsInt); + } + } catch (InvalidValueException e) { + throw runtime.newErrnoEINVALError(); + } + + int fileno = -1; + try { + ChannelDescriptor descriptor = + ChannelDescriptor.open(runtime.getCurrentDirectory(), + path, modes, perms, runtime.getPosix()); + runtime.registerDescriptor(descriptor,true); // isRetained=true + fileno = descriptor.getFileno(); + } + catch (FileNotFoundException fnfe) { + throw runtime.newErrnoENOENTError(path); + } catch (DirectoryAsFileException dafe) { + throw runtime.newErrnoEISDirError(path); + } catch (FileExistsException fee) { + throw runtime.newErrnoEEXISTError(path); + } catch (IOException ioe) { + throw runtime.newIOErrorFromException(ioe); + } + return runtime.newFixnum(fileno); + } + // This appears to be some windows-only mode. On a java platform this is a no-op @JRubyMethod(name = "binmode") public IRubyObject binmode() { Index: test/test_io.rb =================================================================== --- test/test_io.rb (revision 8301) +++ test/test_io.rb (working copy) @@ -254,6 +254,30 @@ assert_raises(Errno::EBADF) { f.close } end + def test_sysopen + ensure_files @file + + fno = IO::sysopen(@file, "r", 0124) # not creating, mode is ignored + assert_instance_of(Fixnum, fno) + assert_raises(Errno::EINVAL) { IO.open(fno, "w") } # not writable + IO.open(fno, "r") do |io| + assert_equal(fno, io.fileno) + assert(!io.closed?) + end + assert_raises(Errno::EBADF) { IO.open(fno, "r") } # fd is closed + File.open(@file) do |f| + mode = (f.stat.mode & 0777) # only comparing lower 9 bits + assert(mode > 0124) + end + + File.delete(@file) + fno = IO::sysopen(@file, "w", 0611) # creating, mode is enforced + File.open(@file) do |f| + mode = (f.stat.mode & 0777) + assert_equal(0611, mode) + end + end + def test_delete ensure_files @file, @file2, @file3 # Test deleting files