Win32, file descriptors and rb_io_check_writable()

Hi all,

Windows 2000/XP
VC++ 6.0 or 7.0
Ruby 1.8.2 (Installer RC9)

I'm trying to write an extension for the win32-file package.
Specifically, I want to write a wrapper for CreateFile() called
File.nopen (for 'native open'). I thought this would be nice for two
reasons. First, it would give you finer control over HANDLE creation
on the Win32 platform. Second, it would allow you to open
directories, since you cannot currently call File.open on a directory
on Windows.

So, the API would look like this:

File.nopen(file, access, share, creation, flags)

The underlying C code looks like this:

static VALUE file_nopen(int argc, VALUE *argv, VALUE klass){
   HANDLE h;
   int rv;
   VALUE args[1];
   VALUE rbFile, rbAccess, rbShare, rbCreation, rbFlags;
   DWORD dwAccess = FILE_ALL_ACCESS; // I tried various flags here
   DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
   DWORD dwCreation = OPEN_EXISTING;
   DWORD dwFlags = FILE_ATTRIBUTE_NORMAL;
   
   rb_scan_args(argc,argv,"14",&rbFile,&rbAccess,&rbShare,&rbCreation,&rbFlags);

   // Override default values with user supplied values
   if(Qnil != rbAccess)
      dwAccess = (DWORD)NUM2UINT(rbAccess);
   
   if(Qnil != rbShare)
      dwShare = (DWORD)NUM2UINT(rbShare);
   
   if(Qnil != rbCreation)
      dwCreation = (DWORD)NUM2UINT(rbCreation);
   
   if(Qnil != rbFlags)
      dwFlags = (DWORD)NUM2UINT(rbFlags);
     
   h = CreateFile(
      StringValuePtr(rbFile),
      dwAccess,
      dwShare,
      NULL, // Cannot be inherited
      dwCreation,
      dwFlags,
      NULL // No template
   );
   
   if(h == INVALID_HANDLE_VALUE)
      rb_raise(cFileError,ErrorDescription(GetLastError()));
      
   // Convert HANDLE to file descriptor
   args[0] = UINT2NUM(_open_osfhandle((long)h,O_RDWR));
   
   // Return a bonafide File object, based on the file descriptor
   return rb_class_new_instance(1,args,rb_cFile);
}

For read operations, this works. I can do this:

fh = File.nopen("C:\\somefile")
p fh.readlines # works fine
fh.close

I can even read from a directory:

fh = File.nopen("C:\\TESTDIR",nil,nil,nil,File::BACKUP_SEMANTICS)
fh.close

But, I cannot write to the filehandle:

fh = File.nopen("C:\\somefile")
fh.puts "Hello"

test.rb:13:in `write': not opened for writing (IOError)
        from test.rb:13:in `puts'
        from test.rb:13

That error appears to be coming from the rb_io_check_writable()
function, which in turn is checking the mode of the OpenFile struct
defined in rubyio.h.

How do I deal with this? Am I doing something wrong? Or would it
take a modification of io.c for this to work? If so, what should be
done?

Regards,

Dan

Hi,

Hi all,

Windows 2000/XP
VC++ 6.0 or 7.0
Ruby 1.8.2 (Installer RC9)

I'm trying to write an extension for the win32-file package.
Specifically, I want to write a wrapper for CreateFile() called
File.nopen (for 'native open'). I thought this would be nice for two
reasons. First, it would give you finer control over HANDLE creation
on the Win32 platform. Second, it would allow you to open
directories, since you cannot currently call File.open on a directory
on Windows.

So, the API would look like this:

File.nopen(file, access, share, creation, flags)

The underlying C code looks like this:

...

For read operations, this works. I can do this:

fh = File.nopen("C:\\somefile")
p fh.readlines # works fine
fh.close

But, I cannot write to the filehandle:

fh = File.nopen("C:\\somefile")
fh.puts "Hello"

test.rb:13:in `write': not opened for writing (IOError)
       from test.rb:13:in `puts'
       from test.rb:13

That error appears to be coming from the rb_io_check_writable()
function, which in turn is checking the mode of the OpenFile struct
defined in rubyio.h.

How do I deal with this? Am I doing something wrong? Or would it
take a modification of io.c for this to work? If so, what should be
done?

Following code will work:

#define OpenFile WINAPI_OpenFile
#include <windows.h>
...
#undef OpenFile
#include "ruby.h"
#include "rubyio.h"
...
static VALUE file_nopen(int argc, VALUE *argv, VALUE klass){
   HANDLE h;
   int rv;
   VALUE args[1];
   VALUE rbFile, rbAccess, rbShare, rbCreation, rbFlags;
   DWORD dwAccess = FILE_ALL_ACCESS; // I tried various flags here
   DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
   DWORD dwCreation = OPEN_EXISTING;
   DWORD dwFlags = FILE_ATTRIBUTE_NORMAL;

   rb_scan_args(argc,argv,"14",&rbFile,&rbAccess,&rbShare,&rbCreation,&rbFlags);

   // Override default values with user supplied values
   if(Qnil != rbAccess)
      dwAccess = (DWORD)NUM2UINT(rbAccess);

   if(Qnil != rbShare)
      dwShare = (DWORD)NUM2UINT(rbShare);

   if(Qnil != rbCreation)
      dwCreation = (DWORD)NUM2UINT(rbCreation);

   if(Qnil != rbFlags)
      dwFlags = (DWORD)NUM2UINT(rbFlags);

   h = CreateFile(
      StringValuePtr(rbFile),
      dwAccess,
      dwShare,
      NULL, // Cannot be inherited
      dwCreation,
      dwFlags,
      NULL // No template
   );

   if(h == INVALID_HANDLE_VALUE)
      rb_raise(cFileError,ErrorDescription(GetLastError()));

   // Convert HANDLE to file descriptor
   args[0] = UINT2NUM(_open_osfhandle((long)h,O_RDWR));

   RFILE(self)->fptr->mode = rb_io_mode_flags("w+"); // mode string correspond to dwAccess
   RFILE(self)->fptr->f = rb_fdopen(NUM2INT(args[0]),"w+"); // mode string correspond to dwAccess

   // Return a bonafide File object, based on the file descriptor
   return rb_class_new_instance(1,args,rb_cFile);
}

Regards,

Dan

Regards,

Park Heesob

Hi,

At Sat, 13 Nov 2004 08:13:26 +0900,
Daniel Berger wrote in [ruby-talk:120101]:

But, I cannot write to the filehandle:

fh = File.nopen("C:\\somefile")
fh.puts "Hello"

test.rb:13:in `write': not opened for writing (IOError)
        from test.rb:13:in `puts'
        from test.rb:13

That error appears to be coming from the rb_io_check_writable()
function, which in turn is checking the mode of the OpenFile struct
defined in rubyio.h.

How do I deal with this? Am I doing something wrong? Or would it
take a modification of io.c for this to work? If so, what should be
done?

File#initialize accepts the mode in Fixnum.

   // Convert HANDLE to file descriptor
   args[0] = UINT2NUM(_open_osfhandle((long)h,O_RDWR));

     switch (dwAccess & (GENERIC_READ|GENERIC_WRITE)) {
       case 0:
         args[1] = INT2FIX(0);
         break;
       case GENERIC_READ:
         args[1] = INT2FIX(O_RDONLY);
         break;
       case GENERIC_WRITE:
         args[1] = INT2FIX(O_WRONLY);
         break;
       default:
         args[1] = INT2FIX(O_RDWR);
         break;
     }

   
   // Return a bonafide File object, based on the file descriptor

     return rb_class_new_instance(2,args,klass);

···

}

--
Nobu Nakada

nobu.nokada@softhome.net wrote in message news:<200411150121.iAF1LCVW027655@sharui.nakada.niregi.kanuma.tochigi.jp>...

Hi,

At Sat, 13 Nov 2004 08:13:26 +0900,
Daniel Berger wrote in [ruby-talk:120101]:
> But, I cannot write to the filehandle:
>
> fh = File.nopen("C:\\somefile")
> fh.puts "Hello"
>
> test.rb:13:in `write': not opened for writing (IOError)
> from test.rb:13:in `puts'
> from test.rb:13
>
> That error appears to be coming from the rb_io_check_writable()
> function, which in turn is checking the mode of the OpenFile struct
> defined in rubyio.h.
>
> How do I deal with this? Am I doing something wrong? Or would it
> take a modification of io.c for this to work? If so, what should be
> done?

File#initialize accepts the mode in Fixnum.

> // Convert HANDLE to file descriptor
> args[0] = UINT2NUM(_open_osfhandle((long)h,O_RDWR));
     switch (dwAccess & (GENERIC_READ|GENERIC_WRITE)) {
       case 0:
         args[1] = INT2FIX(0);
         break;
       case GENERIC_READ:
         args[1] = INT2FIX(O_RDONLY);
         break;
       case GENERIC_WRITE:
         args[1] = INT2FIX(O_WRONLY);
         break;
       default:
         args[1] = INT2FIX(O_RDWR);
         break;
     }
>
> // Return a bonafide File object, based on the file descriptor
  return rb_class_new_instance(2,args,klass);
> }

This code does eliminate that error. However, it seems that the write
operations still don't work properly. I can call fh.puts "hello" and,
although no error is raised, nothing is actually written to the file.

Any thoughts?

Dan

"Park Heesob" <phasis@bcline.com> wrote in message news:<003c01c4c91f$1c2718f0$bcdefea9@2xnm9896kmqn5b9>...
<snip>

>
Following code will work:

#define OpenFile WINAPI_OpenFile
#include <windows.h>
..
#undef OpenFile
#include "ruby.h"
#include "rubyio.h"
..

<snip>

   // Convert HANDLE to file descriptor
   args[0] = UINT2NUM(_open_osfhandle((long)h,O_RDWR));

   RFILE(self)->fptr->mode = rb_io_mode_flags("w+"); // mode string correspond to dwAccess
   RFcorrespond to dwAccess

   // Return a bonafide File object, based on the file descriptor
   return rb_class_new_instance(1,args,rb_cFile);
}

Regards,

Park Heesob

Thank you very much Park. There is one curious difference I noticed
with the write operations. It seems that there is a line ending issue
between a file descriptor opened with File.open vs File.nopen. You
can see this easily enough by doing something like this:

fh1 = File.open("test1.txt","w+")
fh1.print "hello\nworld\n"
fh1.close

Open this up with notepad - looks fine. Now try this:

fh2 = File.nopen("test2.txt",nil,nil,File::OPEN_ALWAYS)
fh2.print "hello\nworld\n"
fh2.close

Opened with notepad, you'll see that the line endings aren't the same
(although wordpad handles them properly). I guess I should find an
octal dump tool for windows to verify. Hopefully, you'll see what I
mean.

Regards,

Dan