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