1#! /usr/bin/perl 2 3# ex:ts=8 sw=4: 4# $OpenBSD: FwUpdate.pm,v 1.18 2015/04/16 13:29:16 espie Exp $ 5# 6# Copyright (c) 2014 Marc Espie <espie@openbsd.org> 7# 8# Permission to use, copy, modify, and distribute this software for any 9# purpose with or without fee is hereby granted, provided that the above 10# copyright notice and this permission notice appear in all copies. 11# 12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19# 20use strict; 21use warnings; 22use OpenBSD::PkgAdd; 23use OpenBSD::PackageRepository; 24 25package OpenBSD::FwUpdate::State; 26our @ISA = qw(OpenBSD::PkgAdd::State); 27 28sub find_path 29{ 30 my $state = shift; 31 my $dir = OpenBSD::Paths->os_directory; 32 if (defined $dir) { 33 $state->{path} = "http://firmware.openbsd.org/firmware/$dir/"; 34 } else { 35 $state->fatal("Couldn't find/parse OS version"); 36 } 37} 38 39sub handle_options 40{ 41 my $state = shift; 42 $state->OpenBSD::State::handle_options('adinp:', 43 '[-adinv] [-D keyword] [-p path] [driver...]'); 44 $state->{not} = $state->opt('n'); 45 if ($state->opt('p')) { 46 $state->{path} = $state->opt('p'); 47 } else { 48 $state->find_path; 49 } 50 if ($state->opt('i')) { 51 $state->{not} = 1; 52 } 53 $main::not = $state->{not}; 54 $state->progress->setup(0, 0, $state); 55 $state->{localbase} = OpenBSD::Paths->localbase; 56 $state->{destdir} = ''; 57 $state->{wantntogo} = 0; 58 $state->{interactive} = OpenBSD::InteractiveStub->new($state); 59 $state->{subst}->add('repair', 1); 60 if ($state->opt('a') && @ARGV != 0) { 61 $state->usage; 62 } 63 $state->{fw_repository} = 64 OpenBSD::PackageRepository->new($state->{path}, $state); 65 $state->{fw_verbose} = $state->{v}; 66 if ($state->{v}) { 67 $state->{v}--; 68 } 69 if ($state->{fw_verbose}) { 70 $state->say("Path to firmware: #1", $state->{path}); 71 } 72 $state->{subst}->add('NO_SCP', 1); 73} 74 75sub finish_init 76{ 77 my $state = shift; 78 delete $state->{signer_list}; # XXX uncache value 79 $state->{subst}->add('FW_UPDATE', 1); 80} 81 82sub installed_drivers 83{ 84 my $self = shift; 85 return keys %{$self->{installed_drivers}}; 86} 87 88sub is_installed 89{ 90 my ($self, $driver) = @_; 91 return $self->{installed_drivers}{$driver}; 92} 93 94sub machine_drivers 95{ 96 my $self = shift; 97 return keys %{$self->{machine_drivers}}; 98} 99 100sub all_drivers 101{ 102 my $self = shift; 103 return keys %{$self->{all_drivers}}; 104} 105 106sub is_needed 107{ 108 my ($self, $driver) = @_; 109 return $self->{machine_drivers}{$driver}; 110} 111 112sub display_timestamp 113{ 114 my ($state, $pkgname, $timestamp) = @_; 115 return unless $state->verbose; 116 $state->SUPER::display_timestamp($pkgname, $timestamp); 117} 118 119sub fw_status 120{ 121 my ($state, $msg, $list) = @_; 122 return if @$list == 0; 123 $state->say("#1: #2", $msg, join(' ', @$list)); 124} 125 126package OpenBSD::FwUpdate::Update; 127our @ISA = qw(OpenBSD::Update); 128 129package OpenBSD::FwUpdate; 130our @ISA = qw(OpenBSD::PkgAdd); 131 132OpenBSD::Auto::cache(updater, 133 sub { 134 require OpenBSD::Update; 135 return OpenBSD::FwUpdate::Update->new; 136 }); 137 138my %possible_drivers = map {($_, 1)} 139 (qw(acx athn bwi ipw iwi iwm iwn malo otus pgt radeondrm rsu uath 140 upgt urtwn uvideo wpi)); 141 142 143sub parse_dmesg 144{ 145 my ($self, $f, $search, $found) = @_; 146 147 while (<$f>) { 148 chomp; 149 for my $driver (keys %$search) { 150 next unless m/^\Q$driver\E\d+\s+at\s/; 151 delete $search->{$driver}; 152 $found->{$driver} = 1; 153 } 154 } 155} 156 157sub find_machine_drivers 158{ 159 my ($self, $state) = @_; 160 $state->{machine_drivers} = {}; 161 $state->{all_drivers} = \%possible_drivers; 162 my %search = %possible_drivers; 163 if (open(my $f, '<', '/var/run/dmesg.boot')) { 164 $self->parse_dmesg($f, \%search, $state->{machine_drivers}); 165 close($f); 166 } else { 167 $state->errsay("Can't open dmesg.boot: #1", $!); 168 } 169 if (open(my $cmd, '-|', 'dmesg')) { 170 $self->parse_dmesg($cmd, \%search, $state->{machine_drivers}); 171 close($cmd); 172 } else { 173 $state->errsay("Can't run dmesg: #1", $!); 174 } 175} 176 177sub driver2firmware 178{ 179 return shift."-firmware"; 180} 181 182sub find_installed_drivers 183{ 184 my ($self, $state) = @_; 185 my $inst = $state->repo->installed; 186 for my $driver (keys %possible_drivers) { 187 my $search = OpenBSD::Search::Stem->new(driver2firmware($driver)); 188 my $l = $inst->match_locations($search); 189 if (@$l > 0) { 190 $state->{installed_drivers}{$driver} = 191 OpenBSD::Handle->from_location($l->[0]); 192 } 193 } 194} 195 196 197sub new_state 198{ 199 my ($self, $cmd) = @_; 200 return OpenBSD::FwUpdate::State->new($cmd); 201} 202 203sub find_handle 204{ 205 my ($self, $state, $driver) = @_; 206 my $pkgname = driver2firmware($driver); 207 my $set; 208 my $h = $state->is_installed($driver); 209 if ($h) { 210 $set = $state->updateset->add_older($h); 211 } else { 212 $set = $state->updateset->add_hints($pkgname); 213 } 214 $set->add_repositories($state->{fw_repository}); 215 return $set; 216} 217 218sub mark_set_for_deletion 219{ 220 my ($self, $set, $state) = @_; 221 # XXX to be simplified. Basically, we pre-do the work of the updater... 222 for my $h ($set->older) { 223 $h->{update_found} = 1; 224 } 225 $set->{updates}++; 226} 227 228# no way we can find a new quirks on the firmware site 229sub do_quirks 230{ 231 my ($self, $state) = @_; 232 $self->SUPER::do_quirks($state); 233 $state->finish_init; 234} 235 236sub to_remove 237{ 238 my ($self, $state, $driver) = @_; 239 $self->mark_set_for_deletion($self->to_add_or_update($state, $driver)); 240} 241 242sub to_add_or_update 243{ 244 my ($self, $state, $driver) = @_; 245 my $set = $self->find_handle($state, $driver); 246 push(@{$state->{setlist}}, $set); 247 return $set; 248} 249 250sub show_info 251{ 252 my ($self, $state) = @_; 253 my (@installed, @unneeded, @needed); 254 for my $d ($state->installed_drivers) { 255 my $h = $state->is_installed($d)->pkgname; 256 if ($state->is_needed($d)) { 257 push(@installed, $h); 258 } else { 259 push(@unneeded, $h); 260 } 261 } 262 for my $d ($state->machine_drivers) { 263 if (!$state->is_installed($d)) { 264 push(@needed, driver2firmware($d)); 265 } 266 } 267 $state->fw_status("Installed", \@installed); 268 $state->fw_status("Installed, extra", \@unneeded); 269 $state->fw_status("Missing", \@needed); 270} 271 272sub process_parameters 273{ 274 my ($self, $state) = @_; 275 276 $self->find_machine_drivers($state); 277 $self->find_installed_drivers($state); 278 279 if ($state->opt('i')) { 280 $self->show_info($state); 281 exit(0); 282 } 283 if (@ARGV == 0) { 284 if ($state->opt('d')) { 285 for my $driver ($state->installed_drivers) { 286 if ($state->opt('a') || 287 !$state->is_needed($driver)) { 288 $self->to_remove($state, $driver); 289 } 290 } 291 } else { 292 if ($state->opt('a')) { 293 for my $driver ($state->all_drivers) { 294 $self->to_add_or_update($state, 295 $driver); 296 } 297 } else { 298 for my $driver ($state->machine_drivers) { 299 $self->to_add_or_update($state, 300 $driver); 301 } 302 for my $driver ($state->installed_drivers) { 303 # XXX skip already done up there ^ 304 next if $state->is_needed($driver); 305 $self->to_add_or_update($state, 306 $driver); 307 } 308 } 309 } 310 if (!(defined $state->{setlist}) && $state->{fw_verbose}) { 311 $state->say($state->opt('d') ? 312 "No firmware to delete." : 313 "No devices found which need firmware files to be downloaded."); 314 exit(0); 315 } 316 } else { 317 for my $driver (@ARGV) { 318 $driver =~ s/\-firmware(\-\d.*)?$//; 319 if (!$possible_drivers{$driver}) { 320 $state->errsay("#1: unknown driver", $driver); 321 exit(1); 322 } 323 if ($state->opt('d') && 324 !$state->is_installed($driver)) { 325 $state->errsay("Can't delete uninstalled driver: #1", $driver); 326 next; 327 } 328 329 my $set = $self->to_add_or_update($state, $driver); 330 if ($state->opt('d')) { 331 $self->mark_set_for_deletion($set); 332 } 333 } 334 } 335 if ($state->{fw_verbose}) { 336 my (@deleting, @updating, @installing); 337 for my $set (@{$state->{setlist}}) { 338 for my $h ($set->older) { 339 if ($h->{update_found}) { 340 push(@deleting, $h->pkgname); 341 } else { 342 push(@updating, $h->pkgname); 343 } 344 } 345 for my $h ($set->hints) { 346 push(@installing, $h->pkgname); 347 } 348 } 349 $state->fw_status("Installing", \@installing); 350 $state->fw_status("Deleting", \@deleting); 351 $state->fw_status("Updating", \@updating); 352 } 353} 354 3551; 356