xref: /openbsd/usr.sbin/pkg_add/OpenBSD/State.pm (revision 76be6724)
1# ex:ts=8 sw=4:
2# $OpenBSD: State.pm,v 1.77 2023/11/25 10:18:40 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 v5.36;
20
21package OpenBSD::PackageRepositoryFactory;
22sub new($class, $state)
23{
24	return bless {state => $state}, $class;
25}
26
27sub locator($self)
28{
29	return $self->{state}->locator;
30}
31
32sub installed($self, $all = 0)
33{
34	require OpenBSD::PackageRepository::Installed;
35
36	return OpenBSD::PackageRepository::Installed->new($all, $self->{state});
37}
38
39sub path_parse($self, $pkgname)
40{
41	return $self->locator->path_parse($pkgname, $self->{state});
42}
43
44sub find($self, $pkg)
45{
46	return $self->locator->find($pkg, $self->{state});
47}
48
49sub reinitialize($)
50{
51}
52
53sub match_locations($self, @p)
54{
55	return $self->locator->match_locations(@p, $self->{state});
56}
57
58sub grabPlist($self, $url, $code)
59{
60	return $self->locator->grabPlist($url, $code, $self->{state});
61}
62
63sub path($self)
64{
65	require OpenBSD::PackageRepositoryList;
66
67	return OpenBSD::PackageRepositoryList->new($self->{state});
68}
69
70# common routines to everything state.
71# in particular, provides "singleton-like" access to UI.
72package OpenBSD::State;
73use OpenBSD::Subst;
74use OpenBSD::Error;
75use parent qw(OpenBSD::BaseState Exporter);
76our @EXPORT = ();
77
78sub locator($)
79{
80	require OpenBSD::PackageLocator;
81	return "OpenBSD::PackageLocator";
82}
83
84sub cache_directory($)
85{
86	return undef;
87}
88
89sub new($class, $cmd = undef, @p)
90{
91	if (!defined $cmd) {
92		$cmd = $0;
93		$cmd =~ s,.*/,,;
94	}
95	my $o = bless {cmd => $cmd}, $class;
96	$o->init(@p);
97	return $o;
98}
99
100sub init($self)
101{
102	$self->{subst} = OpenBSD::Subst->new;
103	$self->{repo} = OpenBSD::PackageRepositoryFactory->new($self);
104	$self->{export_level} = 1;
105	$SIG{'CONT'} = sub {
106		$self->handle_continue;
107	}
108}
109
110sub repo($self)
111{
112	return $self->{repo};
113}
114
115sub handle_continue($self)
116{
117	$self->find_window_size;
118	# invalidate cache so this runs again after continue
119	delete $self->{can_output};
120}
121
122OpenBSD::Auto::cache(can_output,
123	sub($) {
124		require POSIX;
125
126		return 1 if !-t STDOUT;
127		# XXX uses POSIX semantics so fd, we can hardcode stdout ;)
128		my $s = POSIX::tcgetpgrp(1);
129		# note that STDOUT may be redirected
130		# (tcgetpgrp() returns 0 for pipes and -1 for files)
131		# (we shouldn't be there because of the tty test)
132		return $s <= 0 || getpgrp() == $s;
133	});
134
135OpenBSD::Auto::cache(installpath,
136	sub($self) {
137		return undef if $self->defines('NOINSTALLPATH');
138		require OpenBSD::Paths;
139		open(my $fh, '<', OpenBSD::Paths->installurl) or return undef;
140		while (<$fh>) {
141			chomp;
142			next if m/^\s*\#/;
143			next if m/^\s*$/;
144			return "$_/%c/packages/%a/";
145		}
146	});
147
148OpenBSD::Auto::cache(shlibs,
149	sub($self) {
150		require OpenBSD::SharedLibs;
151		return $self->{shlibs} //= OpenBSD::SharedLibs->new($self);
152	});
153
154sub usage_is($self, @usage)
155{
156	$self->{usage} = \@usage;
157}
158
159sub verbose($self)
160{
161	return $self->{v};
162}
163
164sub opt($self, $k)
165{
166	return $self->{opt}{$k};
167}
168
169sub usage($self, @p)
170{
171	my $code = 0;
172	if (@p) {
173		print STDERR "$self->{cmd}: ", $self->f(@p), "\n";
174		$code = 1;
175	}
176	print STDERR "Usage: $self->{cmd} ", shift(@{$self->{usage}}), "\n";
177	for my $l (@{$self->{usage}}) {
178		print STDERR "       $l\n";
179	}
180	exit($code);
181}
182
183sub do_options($state, $sub)
184{
185	# this could be nicer...
186
187	try {
188		&$sub();
189	} catch {
190		$state->usage("#1", $_);
191	};
192}
193
194sub validate_usage($state, $string, @usage)
195{
196	my $h = {};
197	my $h2 = {};
198	my $previous;
199	for my $letter (split //, $string) {
200		if ($letter eq ':') {
201			$h->{$previous} = 1;
202		} else {
203			$previous = $letter;
204			$h->{$previous} = 0;
205		}
206	}
207	for my $u (@usage) {
208		while ($u =~ s/\[\-(.*?)\]//) {
209			my $opts = $1;
210			if ($opts =~ m/^[A-Za-z]+$/) {
211				for my $o (split //, $opts) {
212					$h2->{$o} = 0;
213				}
214			} else {
215				$opts =~ m/./;
216				$h2->{$&} = 1;
217			}
218		}
219	}
220	for my $k (keys %$h) {
221		if (!exists $h2->{$k}) {
222			    $state->errsay("Option #1 #2is not in usage", $k,
223				$h->{$k} ? "(with params) " : "");
224		} elsif ($h2->{$k} != $h->{$k}) {
225			$state->errsay("Discrepancy for option #1", $k);
226		}
227	}
228	for my $k (keys %$h2) {
229		if (!exists $h->{$k}) {
230			$state->errsay("Option #1 does not exist", $k);
231		}
232	}
233}
234
235sub handle_options($state, $opt_string, @usage)
236{
237	require OpenBSD::Getopt;
238
239	$state->{opt}{v} = 0 unless $opt_string =~ m/v/;
240	$state->{opt}{h} =
241	    sub() {
242	    	$state->usage;
243	    } unless $opt_string =~ m/h/;
244	$state->{opt}{D} =
245	    sub($opt) {
246		$state->{subst}->parse_option($opt);
247	    } unless $opt_string =~ m/D/;
248	$state->usage_is(@usage);
249	$state->do_options(sub() {
250		OpenBSD::Getopt::getopts($opt_string.'hvD:', $state->{opt});
251	});
252	$state->{v} = $state->opt('v');
253
254	# XXX don't try to move to AddCreateDelete, PkgInfo needs this too
255	if ($state->defines('unsigned')) {
256		$state->{signature_style} //= 'unsigned';
257	} elsif ($state->defines('oldsign')) {
258		$state->fatal('old style signature no longer supported');
259	} else {
260		$state->{signature_style} //= 'new';
261	}
262
263	if ($state->defines('VALIDATE_USAGE')) {
264		$state->validate_usage($opt_string.'vD:', @usage);
265	}
266	return if $state->{no_exports};
267	# TODO make sure nothing uses this
268	no strict "refs";
269	no strict "vars";
270	for my $k (keys %{$state->{opt}}) {
271		${"opt_$k"} = $state->opt($k);
272		push(@EXPORT, "\$opt_$k");
273	}
274	local $Exporter::ExportLevel = $state->{export_level};
275	OpenBSD::State->import;
276}
277
278sub defines($self, $k)
279{
280	return $self->{subst}->value($k);
281}
282
283sub width($self)
284{
285	if (!defined $self->{width}) {
286		$self->find_window_size;
287	}
288	return $self->{width};
289}
290
291sub height($self)
292{
293	if (!defined $self->{height}) {
294		$self->find_window_size;
295	}
296	return $self->{height};
297}
298
299sub find_window_size($self)
300{
301	require Term::ReadKey;
302	my @l = Term::ReadKey::GetTermSizeGWINSZ(\*STDOUT);
303	# default to sane values
304	$self->{width} = 80;
305	$self->{height} = 24;
306	if (@l == 4) {
307		# only use what we got if sane
308		$self->{width} = $l[0] if $l[0] > 0;
309		$self->{height} = $l[1] if $l[1] > 0;
310		$SIG{'WINCH'} = sub {
311			$self->find_window_size;
312		};
313	}
314}
315
3161;
317