1# ex:ts=8 sw=4: 2# $OpenBSD: BaseState.pm,v 1.4 2023/08/30 12:04:09 espie Exp $ 3# 4# Copyright (c) 2007-2022 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::BaseState; 22use Carp; 23 24sub can_output($) 25{ 26 1; 27} 28sub sync_display($) 29{ 30} 31 32my $forbidden = qr{[^[:print:]\s]}; 33 34sub safe($self, $string) 35{ 36 $string =~ s/$forbidden/?/g; 37 return $string; 38} 39 40sub f($self, @p) 41{ 42 if (@p == 0) { 43 return undef; 44 } 45 my ($fmt, @l) = @p; 46 47 # is there anything to format, actually ? 48 if ($fmt =~ m/\#\d/) { 49 # encode any unknown chars as ? 50 for (@l) { 51 s/$forbidden/?/g if defined; 52 } 53 # make it so that #0 is # 54 unshift(@l, '#'); 55 $fmt =~ s,\#(\d+),($l[$1] // "<Undefined #$1>"),ge; 56 } 57 return $fmt; 58} 59 60sub _fatal($self, @p) 61{ 62 # implementation note: to print "fatal errors" elsewhere, 63 # the way is to eval { croak @_}; and decide what to do with $@. 64 delete $SIG{__DIE__}; 65 $self->sync_display; 66 croak @p, "\n"; 67} 68 69sub fatal($self, @p) 70{ 71 $self->_fatal($self->f(@p)); 72} 73 74sub _fhprint($self, $fh, @p) 75{ 76 $self->sync_display; 77 print $fh @p; 78} 79sub _print($self, @p) 80{ 81 $self->_fhprint(\*STDOUT, @p) if $self->can_output; 82} 83 84sub _errprint($self, @p) 85{ 86 $self->_fhprint(\*STDERR, @p); 87} 88 89sub fhprint($self, $fh, @p) 90{ 91 $self->_fhprint($fh, $self->f(@p)); 92} 93 94sub fhsay($self, $fh, @p) 95{ 96 if (@p == 0) { 97 $self->_fhprint($fh, "\n"); 98 } else { 99 $self->_fhprint($fh, $self->f(@p), "\n"); 100 } 101} 102 103sub print($self, @p) 104{ 105 $self->fhprint(\*STDOUT, @p) if $self->can_output; 106} 107 108sub say($self, @p) 109{ 110 $self->fhsay(\*STDOUT, @p) if $self->can_output; 111} 112 113sub errprint($self, @p) 114{ 115 $self->fhprint(\*STDERR, @p); 116} 117 118sub errsay($self, @p) 119{ 120 $self->fhsay(\*STDERR, @p); 121} 122 123my @signal_name = (); 124sub fillup_names($) 125{ 126 { 127 # XXX force autoload 128 package verylocal; 129 130 require POSIX; 131 POSIX->import(qw(signal_h)); 132 } 133 134 for my $sym (keys %POSIX::) { 135 next unless $sym =~ /^SIG([A-Z].*)/; 136 my $value = eval "&POSIX::$sym()"; 137 # skip over POSIX stuff we don't have like SIGRT or SIGPOLL 138 next unless defined $value; 139 $signal_name[$value] = $1; 140 } 141 # extra BSD signals 142 $signal_name[5] = 'TRAP'; 143 $signal_name[7] = 'IOT'; 144 $signal_name[10] = 'BUS'; 145 $signal_name[12] = 'SYS'; 146 $signal_name[16] = 'URG'; 147 $signal_name[23] = 'IO'; 148 $signal_name[24] = 'XCPU'; 149 $signal_name[25] = 'XFSZ'; 150 $signal_name[26] = 'VTALRM'; 151 $signal_name[27] = 'PROF'; 152 $signal_name[28] = 'WINCH'; 153 $signal_name[29] = 'INFO'; 154} 155 156sub find_signal($self, $number) 157{ 158 if (@signal_name == 0) { 159 $self->fillup_names; 160 } 161 162 return $signal_name[$number] || $number; 163} 164 165sub child_error($self, $error = $?) 166{ 167 my $extra = ""; 168 169 if ($error & 128) { 170 $extra = $self->f(" (core dumped)"); 171 } 172 if ($error & 127) { 173 return $self->f("killed by signal #1#2", 174 $self->find_signal($error & 127), $extra); 175 } else { 176 return $self->f("exit(#1)#2", ($error >> 8), $extra); 177 } 178} 179 180sub _system($self, @p) 181{ 182 $self->sync_display; 183 my ($todo, $todo2); 184 if (ref $p[0] eq 'CODE') { 185 $todo = shift @p; 186 } else { 187 $todo = sub() {}; 188 } 189 if (ref $p[0] eq 'CODE') { 190 $todo2 = shift @p; 191 } else { 192 $todo2 = sub() {}; 193 } 194 my $r = fork; 195 if (!defined $r) { 196 return 1; 197 } elsif ($r == 0) { 198 $DB::inhibit_exit = 0; 199 &$todo(); 200 exec {$p[0]} @p or 201 exit 1; 202 } else { 203 &$todo2(); 204 waitpid($r, 0); 205 return $?; 206 } 207} 208 209sub system($self, @p) 210{ 211 my $r = $self->_system(@p); 212 if ($r != 0) { 213 if (ref $p[0] eq 'CODE') { 214 shift @p; 215 } 216 if (ref $p[0] eq 'CODE') { 217 shift @p; 218 } 219 $self->errsay("system(#1) failed: #2", 220 join(", ", @p), $self->child_error); 221 } 222 return $r; 223} 224 225sub verbose_system($self, @p) 226{ 227 if (ref $p[0]) { 228 shift @p; 229 } 230 if (ref $p[0]) { 231 shift @p; 232 } 233 234 $self->print("Running #1", join(' ', @p)); 235 my $r = $self->_system(@p); 236 if ($r != 0) { 237 $self->say("... failed: #1", $self->child_error); 238 } else { 239 $self->say; 240 } 241} 242 243sub copy_file($self, @p) 244{ 245 require File::Copy; 246 247 my $r = File::Copy::copy(@p); 248 if (!$r) { 249 $self->say("copy(#1) failed: #2", join(',', @p), $!); 250 } 251 return $r; 252} 253 254sub unlink($self, $verbose, @p) 255{ 256 my $r = unlink @p; 257 if ($r != @p) { 258 $self->say("rm #1 failed: removed only #2 targets, #3", 259 join(' ', @p), $r, $!); 260 } elsif ($verbose) { 261 $self->say("rm #1", join(' ', @p)); 262 } 263 return $r; 264} 265 266sub copy($self, @p) 267{ 268 require File::Copy; 269 270 my $r = File::Copy::copy(@p); 271 if (!$r) { 272 $self->say("copy(#1) failed: #2", join(',', @p), $!); 273 } 274 return $r; 275} 276 2771; 278