File: Passthrough.pm

package info (click to toggle)
debconf 1.5.92
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,176 kB
  • sloc: perl: 8,500; sh: 262; python: 182; makefile: 147
file content (427 lines) | stat: -rw-r--r-- 9,675 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
#!/usr/bin/perl

=head NAME

Debconf::FrontEnd::Passthrough - pass-through meta-frontend for Debconf

=cut

package Debconf::FrontEnd::Passthrough;
use warnings;
use strict;
use Carp;
use IO::Socket;
use IO::Handle;
use IO::Select;
use Debconf::FrontEnd;
use Debconf::Element;
use Debconf::Element::Select;
use Debconf::Element::Multiselect;
use Debconf::Log qw(:all);
use Debconf::Encoding;
use base qw(Debconf::FrontEnd);

=head1 DESCRIPTION

This is a IPC pass-through frontend for Debconf. It is meant to enable
integration of Debconf frontend components with installation systems.

The basic idea of this frontend is to replay messages between the
ConfModule and an arbitrary UI agent. For the most part, messages are
simply relayed back and forth unchanged.

=head1 METHODS

=over 4

=item init

Set up the pipe to the UI agent and other housekeeping chores.

=cut

sub init {
	my $this=shift;

	# If readfh and writefh were not initialized before (by child class),
	# initialize them from environment
	if (not defined $this->{readfh} or not defined $this->{writefh}) {
		if (not defined $this->init_fh_from_env()) {
			die "Neither DEBCONF_PIPE nor DEBCONF_READFD and DEBCONF_WRITEFD were set\n";
		}
	}

	binmode $this->{readfh}, ":utf8";
	binmode $this->{writefh}, ":utf8";

	$this->{readfh}->autoflush(1);
	$this->{writefh}->autoflush(1);

	# Note: SUPER init is not called, since it does several things
	# inappropriate for passthrough frontends, including clearing the capb.
	$this->elements([]);
	$this->interactive(1);
	$this->need_tty(0);
}

=head2 init_fh_from_env

Initialize file handles from the environment variables: DEBCONF_PIPE - socket,
DEBCONF_READFD and DEBCONF_WRITEFD - file descriptors of the FIFO pipes.

=cut

sub init_fh_from_env {
	my $this = shift;
	my ($socket_path, $readfd, $writefd);

	if (defined $ENV{DEBCONF_PIPE}) {
		my $socket_path = $ENV{DEBCONF_PIPE};
		$this->{readfh} = $this->{writefh} = IO::Socket::UNIX->new(
			Type => SOCK_STREAM,
			Peer => $socket_path
		) || croak "Cannot connect to $socket_path: $!";
		return "socket";
	} elsif (defined $ENV{DEBCONF_READFD} and defined $ENV{DEBCONF_WRITEFD}) {
		$readfd = $ENV{DEBCONF_READFD};
		$writefd = $ENV{DEBCONF_WRITEFD};
		$this->{readfh} = IO::Handle->new_from_fd(int($readfd), "r")
			or croak "Failed to open fd $readfd: $!";
		$this->{writefh} = IO::Handle->new_from_fd(int($writefd), "w")
			or croak "Failed to open fd $writefd: $!";
		return "fifo";
	}
	return;
}

=head2 talk_with_timeout

Communicates with the UI agent. Joins all parameters together to create a
command, sends it to the agent, and reads and processes its reply. If timeout
is specified (the first argument), the subroutine will only wait a speficied
number of seconds for the other end to reply. If timeout occurs, undef will be
returned.

=cut

sub talk_with_timeout {
	my $this=shift;
	my $timeout=shift;
	my $command=join(' ', map { Debconf::Encoding::to_Unicode($_) } @_);
	my $reply;

	my $readfh = $this->{readfh} || croak "Broken pipe";
	my $writefh = $this->{writefh} || croak "Broken pipe";

	debug developer => "----> (passthrough) $command";
	print $writefh $command."\n";
	$writefh->flush;

	if (defined $timeout) {
		my $select = IO::Select->new($readfh);
		return if !$select->can_read($timeout);
	}
	return if ($readfh->eof());

	$reply = <$readfh>;
	chomp($reply);
	debug developer => "<---- (passthrough) $reply";
	my ($tag, $val) = split(' ', $reply, 2);
	$val = '' unless defined $val;
	$val = Debconf::Encoding::convert("UTF-8", $val);

	return ($tag, $val) if wantarray;
	return $tag;
}

=head2 talk

Same as talk_with_timeout() just waits for the answer infinitely.

=cut

sub talk {
	my $this=shift;
	return $this->talk_with_timeout(undef, @_);
}

=head2 makeelement

This frontend doesn't really make use of Elements to interact with the user,
so it uses generic Elements as placeholders (except for select and
multiselect Elements for which it needs translation methods). This method
simply makes one.

=cut

sub makeelement
{
	my $this=shift;
	my $question=shift;

	my $type=$question->type;
	if ($type eq "select" || $type eq "multiselect") {
		$type=ucfirst($type);
		return "Debconf::Element::$type"->new(question => $question);
	} else {
		return Debconf::Element->new(question => $question);
	}
}

=head2 capb_backup

Pass capability information along to the UI agent.

=cut

sub capb_backup
{
	my $this=shift;
	my $val = shift;

	$this->{capb_backup} = $val;
	$this->talk('CAPB', 'backup') if $val;
}

=head2 capb

Gets UI agent capabilities.

=cut

sub capb
{
	my $this=shift;
	my $ret;
	return $this->{capb} if exists $this->{capb};

	($ret, $this->{capb}) = $this->talk('CAPB');
	return $this->{capb} if $ret eq '0';
}

=head2 title

Pass title along to the UI agent.

=cut

sub title
{
	my $this = shift;
	return $this->{title} unless @_;
	my $title = shift;

	$this->{title} = $title;
	$this->talk('TITLE', $title);
}

=head2 settitle

Pass title question name along to the UI agent, along with necessary data
about it.

=cut

sub settitle
{
	my $this = shift;
	my $question = shift;

	$this->{title} = $question->description;

	my $tag = $question->template->template;
	my $type = $question->template->type;
	my $desc = $question->description;
	my $extdesc = $question->extended_description;

	$this->talk('DATA', $tag, 'type', $type);

	if ($desc) {
		$desc =~ s/\n/\\n/g;
		$this->talk('DATA', $tag, 'description', $desc);
	}

	if ($extdesc) {
		$extdesc =~ s/\n/\\n/g;
		$this->talk('DATA', $tag, 'extended_description', $extdesc);
	}

	$this->talk('SETTITLE', $tag);
}

=head2 go

Asks the UI agent to display all pending questions, first using the special
data command to tell it necessary data about them. Then read answers from
the UI agent.

=cut

sub go {
	my $this = shift;

	my @elements=grep { $_->visible } @{$this->elements};
	foreach my $element (@elements) {
		my $question = $element->question;
		my $tag = $question->template->template;
		my $type = $question->template->type;
		my $desc = $question->description;
		my $extdesc = $question->extended_description;
		my $default;
		if ($type eq 'select') {
			$default = $element->translate_default;
		} elsif ($type eq 'multiselect') {
			$default = join ', ', $element->translate_default;
		} else {
			$default = $question->value;
		}

                $this->talk('DATA', $tag, 'type', $type);

		if ($desc) {
			$desc =~ s/\n/\\n/g;
			$this->talk('DATA', $tag, 'description', $desc);
		}

		if ($extdesc) {
			$extdesc =~ s/\n/\\n/g;
			$this->talk('DATA', $tag, 'extended_description',
			            $extdesc);
		}

		if ($type eq "select" || $type eq "multiselect") {
			my $choices = $question->choices;
			$choices =~ s/\n/\\n/g if ($choices);
			$this->talk('DATA', $tag, 'choices', $choices);
		}

		$this->talk('SET', $tag, $default) if $default ne '';

		my @vars=$Debconf::Db::config->variables($question->{name});
		for my $var (@vars) {
			my $val=$Debconf::Db::config->getvariable($question->{name}, $var);
			$val='' unless defined $val;
			$this->talk('SUBST', $tag, $var, $val);
		}

		$this->talk('INPUT', $question->priority, $tag);
	}

	# Tell the agent to display the question(s), and check
	# for a back button.
	if (@elements && (scalar($this->talk('GO')) eq "30") && $this->{capb_backup}) {
		return;
	}

	# Retrieve the answers.
	foreach my $element (@{$this->elements}) {
		if ($element->visible) {
			my $tag = $element->question->template->template;
			my $type = $element->question->template->type;

			my ($ret, $val)=$this->talk('GET', $tag);
			if ($ret eq "0") {
				if ($type eq 'select') {
					$element->value($element->translate_to_C($val));
				} elsif ($type eq 'multiselect') {
					$element->value(join(', ', map { $element->translate_to_C($_) } split(', ', $val)));
				} else {
					$element->value($val);
				}
				debug developer => "Got \"$val\" for $tag";
			}
		} else {
			# "show" noninteractive elements, which don't need
			# to pass through, but may do something when shown.
			$element->show;
		}
	}

	return 1;
}

=head2 progress_data

Send necessary data about any progress bar template to the UI agent.

=cut

sub progress_data {
	my $this=shift;
	my $question=shift;

	my $tag=$question->template->template;
	my $type=$question->template->type;
	my $desc=$question->description;
	my $extdesc=$question->extended_description;

	$this->talk('DATA', $tag, 'type', $type);

	if ($desc) {
		$desc =~ s/\n/\\n/g;
		$this->talk('DATA', $tag, 'description', $desc);
	}

	if ($extdesc) {
		$extdesc =~ s/\n/\\n/g;
		$this->talk('DATA', $tag, 'extended_description', $extdesc);
	}
}

sub progress_start {
	my $this=shift;

	$this->progress_data($_[2]);
	return $this->talk('PROGRESS', 'START', $_[0], $_[1], $_[2]->template->template);
}

sub progress_set {
	my $this=shift;

	return (scalar($this->talk('PROGRESS', 'SET', $_[0])) ne "30");
}

sub progress_step {
	my $this=shift;

	return (scalar($this->talk('PROGRESS', 'STEP', $_[0])) ne "30");
}

sub progress_info {
	my $this=shift;

	$this->progress_data($_[0]);
	return (scalar($this->talk('PROGRESS', 'INFO', $_[0]->template->template)) ne "30");
}

sub progress_stop {
	my $this=shift;

	return $this->talk('PROGRESS', 'STOP');
}

sub shutdown {
	my $this=shift;
	$this->SUPER::shutdown();
	# Close readfh if it is not the same as writefh (in case of socket)
	if (defined $this->{readfh} &&
	   (not defined $this->{writefh} or $this->{readfh} != $this->{writefh}))
	{
		close $this->{readfh};
		delete $this->{readfh};
	}
	if (defined $this->{writefh}) {
		close $this->{writefh};
		delete $this->{writefh};
	}
}

=back

=head1 AUTHOR

Randolph Chung <tausq@debian.org>

=cut

1