Search code examples
rubypathname

Ruby - DOS (Win32) path name to NT (native) path name


I need to get the NT (native) path of a file from the DOS (Win32) path in Ruby (1.9.3).

Meaning, I have the string:

dos_path = "C:\Windows\regedit.exe"

but I need:

nt_path = "\Device\HarddiskVolume1\Windows\regedit.exe"

Is there any way to do so? Thanks!


Solution

  • A MS-DOS device name can be converted to a NT path with the QueryDosDevice function. Calling such a function from Ruby can be done with Fiddle. It is part of Ruby's standard library since 1.9.3. However the following example will only work with 2.0.0 or newer.

    require 'fiddle/import'
    require 'fiddle/types'
    
    module DOS2NT
      extend Fiddle::Importer # makes function importing easier
      dlload 'kernel32.dll'
      include Fiddle::Win32Types # so DWORD can be used instead of unsigned long
      extern 'DWORD QueryDosDeviceW(void*, void*, DWORD)', :stdcall
      extern 'DWORD GetLastError()', :stdcall
      ERROR_INSUFFICIENT_BUFFER = 122
    
      SIZEOF_WCHAR = 2 # sizeof(WCHAR) on Windows
    
      # a generic error class
      class Error < StandardError
      end
    
      def self.dos_device_name_to_path(devicename)
        initial_len = 256
        grow_factor = 2
        # we care about Unicode (Windows uses UTF-16LE)
        devicename.encode!(Encoding::UTF_16LE)
        # create buffer
        buf = "\0\0".force_encoding(Encoding::UTF_16LE) * initial_len
        # call QueryDosDeviceW until the call was successful
        while (written_chars = QueryDosDeviceW(devicename, buf, buf.length)) == 0
          # it wasn't
          case (error = GetLastError())
          # resize buffer as it was too short
          when ERROR_INSUFFICIENT_BUFFER
            buf *= grow_factor
          # other errors like ERROR_FILE_NOT_FOUND (2)
          else
            raise Error, "QueryDosDeviceW failed (GetLastError returned #{error})"
          end
        end
        # truncate buffer (including the null character)
        path = buf[0, written_chars - SIZEOF_WCHAR]
        # transcode the path to UTF-8 as that's usually more useful
        path.encode!(Encoding::UTF_8)
      end
    end
    # example strings from the question
    dos_path = 'C:\Windows\regedit.exe'
    nt_path = '\Device\HarddiskVolume1\Windows\regedit.exe'
    # convert and print inspected result
    p dos_path.sub(/\A[A-Z]:/i) { |m| DOS2NT.dos_device_name_to_path(m) } # => "\\Device\\HarddiskVolume1\\Windows\\regedit.exe"