xref: /openbsd-src/usr.sbin/pkg_add/OpenBSD/State.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1# ex:ts=8 sw=4:
2# $OpenBSD: State.pm,v 1.38 2016/09/14 14:14:22 espie Exp $
3#
4# Copyright (c) 2007-2014 Marc Espie <espie@openbsd.org>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17#
18
19use strict;
20use warnings;
21
22package OpenBSD::Configuration;
23sub new
24{
25	my ($class, $state) = @_;
26	my $self = bless {}, $class;
27	require OpenBSD::Paths;
28	$self->read_file(OpenBSD::Paths->pkgconf, $state);
29	return $self;
30}
31
32sub read_file
33{
34	my ($self, $filename, $state) = @_;
35	open(my $fh, '<', $filename) or return;
36	while (<$fh>) {
37		chomp;
38		next if m/^\s*\#/;
39		next if m/^\s*$/;
40		my ($cmd, $k, $v, $add);
41		my $h = $self;
42		if (($cmd, $k, $add, $v) = m/^\s*(.*?)\.(.*?)\s*(\+?)\=\s*(.*)\s*$/) {
43			next unless $cmd eq $state->{cmd};
44			my $h = $self->{cmd} = {};
45		} elsif (($k, $add, $v) = m/^\s*(.*?)\s*(\+?)\=\s*(.*)\s*$/) {
46		} else {
47			# bad line: should we say so ?
48			$state->errsay("Bad line in #1: #2 (#3)",
49			    $filename, $_, $.);
50			next;
51		}
52		# remove caps
53		$k =~ tr/A-Z/a-z/;
54		if ($add eq '') {
55			$h->{$k} = [$v];
56		} else {
57			push(@{$h->{$k}}, $v);
58		}
59	}
60}
61
62sub ref
63{
64	my ($self, $k) = @_;
65	if (defined $self->{cmd}{$k}) {
66		return $self->{cmd}{$k};
67	} else {
68		return $self->{$k};
69	}
70}
71
72sub value
73{
74	my ($self, $k) = @_;
75	my $r = $self->ref($k);
76	if (!defined $r) {
77		return $r;
78	}
79	if (wantarray) {
80		return @$r;
81	} else {
82		return $r->[0];
83	}
84}
85
86sub istrue
87{
88	my ($self, $k) = @_;
89	my $v = $self->value($k);
90	if (defined $v && $v =~ /^yes$/i) {
91		return 1;
92	} else {
93		return 0;
94	}
95}
96
97package OpenBSD::PackageRepositoryFactory;
98sub new
99{
100	my ($class, $state) = @_;
101	bless {state => $state}, $class;
102}
103
104sub installed
105{
106	my ($self, $all) = @_;
107	require OpenBSD::PackageRepository::Installed;
108
109	return OpenBSD::PackageRepository::Installed->new($all, $self->{state});
110}
111
112sub path_parse
113{
114	my ($self, $pkgname) = @_;
115	require OpenBSD::PackageLocator;
116
117	return OpenBSD::PackageLocator->path_parse($pkgname, $self->{state});
118}
119
120sub find
121{
122	my ($self, $pkg) = @_;
123	require OpenBSD::PackageLocator;
124
125	return OpenBSD::PackageLocator->find($pkg, $self->{state});
126}
127
128sub reinitialize
129{
130}
131
132sub match_locations
133{
134	my $self = shift;
135	require OpenBSD::PackageLocator;
136
137	return OpenBSD::PackageLocator->match_locations(@_, $self->{state});
138}
139
140sub grabPlist
141{
142	my ($self, $url, $code) = @_;
143	require OpenBSD::PackageLocator;
144
145	return OpenBSD::PackageLocator->grabPlist($url, $code, $self->{state});
146}
147
148sub path
149{
150	my $self = shift;
151	require OpenBSD::PackageRepositoryList;
152
153	return OpenBSD::PackageRepositoryList->new($self->{state});
154}
155
156# common routines to everything state.
157# in particular, provides "singleton-like" access to UI.
158package OpenBSD::State;
159use Carp;
160use OpenBSD::Subst;
161use OpenBSD::Error;
162require Exporter;
163our @ISA = qw(Exporter);
164our @EXPORT = ();
165
166sub new
167{
168	my $class = shift;
169	my $cmd = shift;
170	my $o = bless {cmd => $cmd}, $class;
171	$o->init(@_);
172	return $o;
173}
174
175sub init
176{
177	my $self = shift;
178	$self->{subst} = OpenBSD::Subst->new;
179	$self->{repo} = OpenBSD::PackageRepositoryFactory->new($self);
180	$self->{export_level} = 1;
181}
182
183sub repo
184{
185	my $self = shift;
186	return $self->{repo};
187}
188
189sub sync_display
190{
191}
192
193OpenBSD::Auto::cache(config,
194	sub {
195		return OpenBSD::Configuration->new(shift);
196	});
197
198sub usage_is
199{
200	my ($self, @usage) = @_;
201	$self->{usage} = \@usage;
202}
203
204sub verbose
205{
206	my $self = shift;
207	return $self->{v};
208}
209
210sub opt
211{
212	my ($self, $k) = @_;
213	return $self->{opt}{$k};
214}
215
216sub usage
217{
218	my $self = shift;
219	my $code = 0;
220	if (@_) {
221		print STDERR "$self->{cmd}: ", $self->f(@_), "\n";
222		$code = 1;
223	}
224	print STDERR "Usage: $self->{cmd} ", shift(@{$self->{usage}}), "\n";
225	for my $l (@{$self->{usage}}) {
226		print STDERR "       $l\n";
227	}
228	exit($code);
229}
230
231sub f
232{
233	my $self = shift;
234	if (@_ == 0) {
235		return undef;
236	}
237	my ($fmt, @l) = @_;
238	# make it so that #0 is #
239	unshift(@l, '#');
240	$fmt =~ s,\#(\d+),($l[$1] // "<Undefined #$1>"),ge;
241	return $fmt;
242}
243
244sub _fatal
245{
246	my $self = shift;
247	# implementation note: to print "fatal errors" elsewhere,
248	# the way is to eval { croak @_}; and decide what to do with $@.
249	delete $SIG{__DIE__};
250	$self->sync_display;
251	croak "Fatal error: ", @_, "\n";
252}
253
254sub fatal
255{
256	my $self = shift;
257	$self->_fatal($self->f(@_));
258}
259
260sub _fhprint
261{
262	my $self = shift;
263	my $fh = shift;
264	$self->sync_display;
265	print $fh @_;
266}
267sub _print
268{
269	my $self = shift;
270	$self->_fhprint(\*STDOUT, @_);
271}
272
273sub _errprint
274{
275	my $self = shift;
276	$self->_fhprint(\*STDERR, @_);
277}
278
279sub fhprint
280{
281	my $self = shift;
282	my $fh = shift;
283	$self->_fhprint($fh, $self->f(@_));
284}
285
286sub fhsay
287{
288	my $self = shift;
289	my $fh = shift;
290	if (@_ == 0) {
291		$self->_fhprint($fh, "\n");
292	} else {
293		$self->_fhprint($fh, $self->f(@_), "\n");
294	}
295}
296
297sub print
298{
299	my $self = shift;
300	$self->fhprint(\*STDOUT, @_);
301}
302
303sub say
304{
305	my $self = shift;
306	$self->fhsay(\*STDOUT, @_);
307}
308
309sub errprint
310{
311	my $self = shift;
312	$self->fhprint(\*STDERR, @_);
313}
314
315sub errsay
316{
317	my $self = shift;
318	$self->fhsay(\*STDERR, @_);
319}
320
321sub do_options
322{
323	my ($state, $sub) = @_;
324	# this could be nicer...
325
326	try {
327		&$sub;
328	} catchall {
329		$state->usage("#1", $_);
330	};
331}
332
333sub handle_options
334{
335	my ($state, $opt_string, @usage) = @_;
336	require OpenBSD::Getopt;
337
338	$state->{opt}{v} = 0 unless $opt_string =~ m/v/;
339	$state->{opt}{h} = sub { $state->usage; } unless $opt_string =~ m/h/;
340	$state->{opt}{D} = sub {
341		$state->{subst}->parse_option(shift);
342	} unless $opt_string =~ m/D/;
343	$state->usage_is(@usage);
344	$state->do_options(sub {
345		OpenBSD::Getopt::getopts($opt_string.'hvD:', $state->{opt});
346	});
347	$state->{v} = $state->opt('v');
348
349	# XXX switch not flipped
350	if ($state->defines('newsign')) {
351		$state->{signature_style} //= 'new';
352	} elsif ($state->defines('unsigned')) {
353		$state->{signature_style} //= 'unsigned';
354	} elsif ($state->defines('oldsign')) {
355		$state->{signature_style} //= 'old';
356	} else {
357		$state->{signature_style} //= 'old';
358	}
359
360	return if $state->{no_exports};
361	# XXX
362	no strict "refs";
363	no strict "vars";
364	for my $k (keys %{$state->{opt}}) {
365		${"opt_$k"} = $state->opt($k);
366		push(@EXPORT, "\$opt_$k");
367	}
368	local $Exporter::ExportLevel = $state->{export_level};
369	import OpenBSD::State;
370}
371
372sub defines
373{
374	my ($self, $k) = @_;
375	return $self->{subst}->value($k);
376}
377
378sub width
379{
380	my $self = shift;
381	if (!defined $self->{width}) {
382		$self->find_window_size;
383	}
384	return $self->{width};
385}
386
387sub height
388{
389	my $self = shift;
390	if (!defined $self->{height}) {
391		$self->find_window_size;
392	}
393	return $self->{height};
394}
395
396sub find_window_size
397{
398	my $self = shift;
399	require Term::ReadKey;
400	my @l = Term::ReadKey::GetTermSizeGWINSZ(\*STDOUT);
401	if (@l != 4) {
402		$self->{width} = 80;
403		$self->{height} = 24;
404	} else {
405		$self->{width} = $l[0];
406		$self->{height} = $l[1];
407		$SIG{'WINCH'} = sub {
408			$self->find_window_size;
409		};
410	}
411	$SIG{'CONT'} = sub {
412		$self->find_window_size(1);
413	}
414}
415
416OpenBSD::Auto::cache(signer_list,
417	sub {
418		my $self = shift;
419		if ($self->defines('SIGNER')) {
420			return [split /,/, $self->{subst}->value('SIGNER')];
421		} else {
422			if ($self->defines('FW_UPDATE')) {
423				return [qr{^.*fw$}];
424			} else {
425				return [qr{^.*pkg$}];
426			}
427		}
428	});
429
430my @signal_name = ();
431sub fillup_names
432{
433	{
434	# XXX force autoload
435	package verylocal;
436
437	require POSIX;
438	POSIX->import(qw(signal_h));
439	}
440
441	for my $sym (keys %POSIX::) {
442		next unless $sym =~ /^SIG([A-Z].*)/;
443		$signal_name[eval "&POSIX::$sym()"] = $1;
444	}
445	# extra BSD signals
446	$signal_name[5] = 'TRAP';
447	$signal_name[7] = 'IOT';
448	$signal_name[10] = 'BUS';
449	$signal_name[12] = 'SYS';
450	$signal_name[16] = 'URG';
451	$signal_name[23] = 'IO';
452	$signal_name[24] = 'XCPU';
453	$signal_name[25] = 'XFSZ';
454	$signal_name[26] = 'VTALRM';
455	$signal_name[27] = 'PROF';
456	$signal_name[28] = 'WINCH';
457	$signal_name[29] = 'INFO';
458}
459
460sub find_signal
461{
462	my $number =  shift;
463
464	if (@signal_name == 0) {
465		fillup_names();
466	}
467
468	return $signal_name[$number] || $number;
469}
470
471sub child_error
472{
473	my $self = shift;
474	my $error = $?;
475
476	my $extra = "";
477
478	if ($error & 128) {
479		$extra = $self->f(" (core dumped)");
480	}
481	if ($error & 127) {
482		return $self->f("killed by signal #1#2",
483		    find_signal($error & 127), $extra);
484	} else {
485		return $self->f("exit(#1)#2", ($error >> 8), $extra);
486	}
487}
488
489sub _system
490{
491	my $self = shift;
492	$self->sync_display;
493	my $r = fork;
494	my ($todo, $todo2);
495	if (ref $_[0] eq 'CODE') {
496		$todo = shift;
497	} else {
498		$todo = sub {};
499	}
500	if (ref $_[0] eq 'CODE') {
501		$todo2 = shift;
502	} else {
503		$todo2 = sub {};
504	}
505	if (!defined $r) {
506		return 1;
507	} elsif ($r == 0) {
508		&$todo;
509		exec {$_[0]} @_ or return 1;
510	} else {
511		&$todo2;
512		waitpid($r, 0);
513		return $?;
514	}
515}
516
517sub system
518{
519	my $self = shift;
520	my $r = $self->_system(@_);
521	if ($r != 0) {
522		if (ref $_[0] eq 'CODE') {
523			shift;
524		}
525		if (ref $_[0] eq 'CODE') {
526			shift;
527		}
528		$self->say("system(#1) failed: #2",
529		    join(", ", @_), $self->child_error);
530	}
531	return $r;
532}
533
534sub verbose_system
535{
536	my $self = shift;
537	my @p = @_;
538	if (ref $p[0]) {
539		shift @p;
540	}
541	if (ref $p[0]) {
542		shift @p;
543	}
544
545	$self->print("Running #1", join(' ', @p));
546	my $r = $self->_system(@_);
547	if ($r != 0) {
548		$self->say("... failed: #1", $self->child_error);
549	} else {
550		$self->say;
551	}
552}
553
554sub copy_file
555{
556	my $self = shift;
557	require File::Copy;
558
559	my $r = File::Copy::copy(@_);
560	if (!$r) {
561		$self->say("copy(#1) failed: #2", join(',', @_), $!);
562	}
563	return $r;
564}
565
566sub unlink
567{
568	my $self = shift;
569	my $verbose = shift;
570	my $r = unlink @_;
571	if ($r != @_) {
572		$self->say("rm #1 failed: removed only #2 targets, #3",
573		    join(' ', @_), $r, $!);
574	} elsif ($verbose) {
575		$self->say("rm #1", join(' ', @_));
576	}
577	return $r;
578}
579
580sub copy
581{
582	my $self = shift;
583	require File::Copy;
584
585	my $r = File::Copy::copy(@_);
586	if (!$r) {
587		$self->say("copy(#1) failed: #2", join(',', @_), $!);
588	}
589	return $r;
590}
591
5921;
593