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