Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent duplicate VirtualBox usbfilters from being added #5774

Closed
follower opened this issue Jun 2, 2015 · 5 comments
Closed

Prevent duplicate VirtualBox usbfilters from being added #5774

follower opened this issue Jun 2, 2015 · 5 comments

Comments

@follower
Copy link
Contributor

follower commented Jun 2, 2015

The standard approach for "capturing" USB devices on VirtualBox with Vagrant is (via):

vb.customize ['usbfilter', 'add', '0', '--target', :id, '--name', 'USB Blaster-II', '--action', 'hold', '--active', 'yes', '--vendorid', '0x09fb', '--productid', '0x6810']

Unfortunately the underlying VBoxManage usbfilter add ... command does not prevent duplicate filters from being added. This means running Vagrant multiple times results in multiple filters added--which at a minimum is unpleasant.

The ideal solution would be for VBoxManage to have some way of ignoring (exact) duplicate filters addition requests; a slightly less ideal solution would be for the Vagrant VirtualBox provider to ignore (exact) duplicate filter addition requests; and, my extremely un-ideal but "working" solution is to put this code in my Vagrantfile and shudder:

  def usbfilter_exists(vendor_id, product_id)
    #
    # Determine if a usbfilter with the provided Vendor/Product ID combination
    # already exists on this VM.
    #
    # TODO: Use a more reliable way of retrieving this information.
    #
    # NOTE: The "machinereadable" output for usbfilters is more
    #       complicated to work with (due to variable names including
    #       the numeric filter index) so we don't use it here.
    #
    machine_id_filepath = ".vagrant/machines/default/virtualbox/id"

    if not File.exists? machine_id_filepath
      # VM hasn't been created yet.
      return false
    end

    vm_info = `VBoxManage showvminfo $(<#{machine_id_filepath})`
    filter_match = "VendorId:         #{vendor_id}\nProductId:        #{product_id}\n"
    return vm_info.include? filter_match
  end

  def better_usbfilter_add(vb, vendor_id, product_id, filter_name)
    #
    # This is a workaround for the fact VirtualBox doesn't provide
    # a way for preventing duplicate USB filters from being added.
    #
    # TODO: Implement this in a way that it doesn't get run multiple
    #       times on each Vagrantfile parsing.
    #
    if not usbfilter_exists(vendor_id, product_id)
      vb.customize ["usbfilter", "add", "0",
                    "--target", :id,
                    "--name", filter_name,
                    "--vendorid", vendor_id,
                    "--productid", product_id
                    ]
    end
  end

  config.vm.provider "virtualbox" do |vb|

    better_usbfilter_add(vb, "<the_vendor_id>", "<the_product_id>", "<the_filter_name>")

    vb.customize ["modifyvm", :id, "--usb", "on"]
    vb.customize ["modifyvm", :id, "--usbehci", "on"]
  end

In my defence, I'm not a Ruby or Vagrant developer. ;)

You'll need to substitute your desired Product/Vendor ID and filter name where indicated in the code.

Hopefully this is useful for you or encourages someone to develop a better solution. :)

@sethvargo
Copy link
Contributor

Hi @follower

Thank you for opening an issue. As you have discovered, sometimes the VirtualBox APIs (and their behaviors) are less than to be desired 😦. I am glad you came up with a solution. I do not think this warrants adding code in Vagrant core (especially since this is a very specific use case), but I think this would make an excellent plugin!

Please let me know if you have any questions 😄

@AgDude
Copy link

AgDude commented Dec 17, 2015

Thanks @follower for the snippet.

As a note for future readers, if you are on windows you'll need alter the machine_id_filepath to use backslash, or probably use ruby File.join.

@mark-veenstra
Copy link

If you really want to enjoy the USB filters and add all known filters based on vendor ids, you could do something like:

    require 'open-uri'
    source = open('http://www.linux-usb.org/usb.ids', &:read)
    source = source.lines.reject { |line|
      line =~ /^(?![0-9a-f])/
    }.join()
    #puts "#{source}"
    source.lines { |line|
      field = line.gsub(/\s+/m, ' ').strip.split(" ", 2)
      vendorId = "0x#{field[0]}"
      name = "#{field[1]}"
      vb.customize ["usbfilter", "add", "0", "--target", :id, "--name", name, "--vendorid", vendorId]
    }

@larryli
Copy link

larryli commented Apr 28, 2019

# -*- mode: ruby -*-
# vi: set ft=ruby :

class VagrantPlugins::ProviderVirtualBox::Config < Vagrant.plugin("2", :config)
  def update_customizations(customizations)
    @customizations = customizations
  end
end

class VagrantPlugins::ProviderVirtualBox::Action::Customize
  alias_method :original_call, :call
  def call(env)
    machine = env[:machine]
    config = machine.provider_config
    driver = machine.provider.driver
    uuid = driver.instance_eval { @uuid }
    if uuid != nil
      lines = driver.execute('showvminfo', uuid, '--machinereadable', retryable: true).split("\n")
      filters = {}
      lines.each do |line|
        if matcher = /^USBFilterVendorId(\d+)="(.+?)"$/.match(line)
          id = matcher[1].to_i
          vendor_id = matcher[2].to_s
          filters[id] ||= {}
          filters[id][:vendor_id] = vendor_id
        elsif matcher = /^USBFilterProductId(\d+)="(.+?)"$/.match(line)
          id = matcher[1].to_i
          product_id = matcher[2].to_s
          filters[id] ||= {}
          filters[id][:product_id] = product_id
        end
      end
      config.update_customizations(config.customizations.reject { |_, command| filter_exists(filters, command) })
    end
    original_call(env)
  end

  def filter_exists(filters, command)
    if command.size > 6 && command[0] == 'usbfilter' && command[1] == 'add'
      vendor_id = product_id = false
      i = 2
      while i < command.size - 1 do
        if command[i] == '--vendorid'
          i += 1
          vendor_id = command[i]
        elsif command[i] == '--productid'
          i += 1
          product_id = command[i]
        end
        i += 1
      end
      if vendor_id != false && product_id != false
        filters.each do |_, filter|
          if filter[:vendor_id] == vendor_id && filter[:product_id] == product_id
            return true
          end
        end
      end
    end
    return false
  end
end

Vagrant.configure("2") do |config|
  config.vm.provider "virtualbox" do |vb|
    # Enable USB
    vb.customize ['modifyvm', :id, '--usb', 'on']
    # Please use your esp device id, run `VBoxManage.exe list usbhost` list devices
    vb.customize ['usbfilter', 'add', '0', '--target', :id, '--name', 'ESP', '--vendorid', '10c4', '--productid', 'ea60']
  end
end

@ghost
Copy link

ghost commented Apr 1, 2020

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@hashicorp hashicorp locked and limited conversation to collaborators Apr 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants