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