line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::Diskd; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
23056
|
use 5.014; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
28
|
|
4
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
43
|
|
5
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
45
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
1212
|
use POE; |
|
1
|
|
|
|
|
62831
|
|
|
1
|
|
|
|
|
8
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
sub Daemon { |
12
|
|
|
|
|
|
|
|
13
|
0
|
|
|
0
|
|
|
print "Starting diskd in daemon mode\n"; |
14
|
|
|
|
|
|
|
|
15
|
0
|
|
|
|
|
|
my $info = Local::Info->new; |
16
|
|
|
|
|
|
|
|
17
|
0
|
|
|
|
|
|
my $blkid_session = Local::DiskWatcher->new(info => $info,); |
18
|
0
|
|
|
|
|
|
my $usock_session = Local::UnixSocketServer->new(info => $info); |
19
|
0
|
|
|
|
|
|
my $multi_session = Local::MulticastServer->new(info => $info, ttl=>2); |
20
|
|
|
|
|
|
|
|
21
|
0
|
|
|
|
|
|
POE::Kernel->run(); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
} |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub Client { |
26
|
|
|
|
|
|
|
|
27
|
0
|
|
|
0
|
|
|
print "Starting diskd in client mode\n"; |
28
|
|
|
|
|
|
|
|
29
|
0
|
|
|
|
|
|
my $usock_client = Local::UnixSocketClient->new; |
30
|
|
|
|
|
|
|
|
31
|
0
|
|
|
|
|
|
POE::Kernel->run(); |
32
|
|
|
|
|
|
|
} |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
1; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
## |
37
|
|
|
|
|
|
|
## The Info package is intended to provide a central area where we can |
38
|
|
|
|
|
|
|
## store details of known disks and hosts. It just provides some |
39
|
|
|
|
|
|
|
## useful get/set interfaces that the other packages can use. |
40
|
|
|
|
|
|
|
## |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
package Local::Info; |
43
|
|
|
|
|
|
|
|
44
|
1
|
|
|
1
|
|
98628
|
use POE qw(Wheel::Run); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
45
|
|
|
|
|
|
|
|
46
|
1
|
|
|
1
|
|
38014
|
use Sys::Hostname; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
66
|
|
47
|
1
|
|
|
1
|
|
1037
|
use Net::Nslookup; |
|
1
|
|
|
|
|
1103
|
|
|
1
|
|
|
|
|
468
|
|
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub new { |
50
|
0
|
|
|
0
|
|
|
my $class = shift; |
51
|
|
|
|
|
|
|
|
52
|
0
|
|
|
|
|
|
my $hostname = hostname; |
53
|
0
|
|
|
|
|
|
my ($ip) = nslookup $hostname; |
54
|
|
|
|
|
|
|
|
55
|
0
|
|
|
|
|
|
return bless { |
56
|
|
|
|
|
|
|
this_host => $hostname, |
57
|
|
|
|
|
|
|
this_ip => $ip, |
58
|
|
|
|
|
|
|
temp_disk_list => [], |
59
|
|
|
|
|
|
|
disks_by_ip => { $ip => {} }, |
60
|
|
|
|
|
|
|
update_time => {}, |
61
|
|
|
|
|
|
|
}, $class; |
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
|
64
|
0
|
|
|
0
|
|
|
sub our_ip { (shift)->{this_ip} } |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# We update local disk info in two phases, first creating a list to |
67
|
|
|
|
|
|
|
# store them, then inserting the list into the live data all at once. |
68
|
|
|
|
|
|
|
# This is so that we can handle disks being detached from the system |
69
|
|
|
|
|
|
|
# between runs of blkid. |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub add_our_disk_info { |
72
|
|
|
|
|
|
|
|
73
|
0
|
|
|
0
|
|
|
my ($self,$uuid,$label,$device) = @_; |
74
|
|
|
|
|
|
|
|
75
|
0
|
|
|
|
|
|
my $listref = $self->{temp_disk_list}; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# As a mnemonic for the order below, remember that UUIDs are more |
78
|
|
|
|
|
|
|
# unique than labels, which in turn are more unique than device |
79
|
|
|
|
|
|
|
# filenames. |
80
|
0
|
|
|
|
|
|
push @$listref, [$uuid,$label,$device]; |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub commit_our_disk_info { |
85
|
|
|
|
|
|
|
|
86
|
0
|
|
|
0
|
|
|
my $self = shift; |
87
|
0
|
|
|
|
|
|
my $ip = $self->{this_ip}; |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
#warn "comitting new blkid data with " . (0+ @{$self->{temp_disk_list}}) . |
90
|
|
|
|
|
|
|
# " entries\n"; |
91
|
|
|
|
|
|
|
|
92
|
0
|
|
|
|
|
|
$self->{update_time}->{$ip} = time(); |
93
|
0
|
|
|
|
|
|
$self->{disks_by_ip}->{$ip} = $self->{temp_disk_list}; |
94
|
0
|
|
|
|
|
|
$self->{temp_disk_list} = []; |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# TODO: update "last seen" structures for each disk with a label/uuid. |
97
|
|
|
|
|
|
|
# for each structure, map the label/uuid to [ip, timestamp] info. |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub known_hosts { |
101
|
0
|
|
|
0
|
|
|
my $self = shift; |
102
|
0
|
|
|
|
|
|
return keys %{$self->{disks_by_ip}}; |
|
0
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
sub disks_by_host { |
105
|
0
|
|
|
0
|
|
|
my ($self,$host) = @_; |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
#warn "looking up host $host"; |
108
|
0
|
0
|
|
|
|
|
return undef unless exists $self->{disks_by_ip}->{$host}; |
109
|
0
|
|
|
|
|
|
return $self->{disks_by_ip}->{$host}; |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# |
113
|
|
|
|
|
|
|
# The routines used to pack and unpack a list of disks for |
114
|
|
|
|
|
|
|
# transmission could take any form, really. The key things to consider |
115
|
|
|
|
|
|
|
# are that (a) arbitrary spoofed data can't result in us introducing |
116
|
|
|
|
|
|
|
# security issues (so solutions that involve eval'ing the packed data |
117
|
|
|
|
|
|
|
# are out, unless we validate that the data is in the expected form) |
118
|
|
|
|
|
|
|
# and (b) we take into consideration quoting issues (such as not using |
119
|
|
|
|
|
|
|
# spaces as separators, since they may appear in disk labels). As it |
120
|
|
|
|
|
|
|
# happens, YAML can solve both of these problems for us. It may not |
121
|
|
|
|
|
|
|
# make best use of space, but at least it's quick and easy to |
122
|
|
|
|
|
|
|
# implement. |
123
|
|
|
|
|
|
|
# |
124
|
|
|
|
|
|
|
|
125
|
1
|
|
|
1
|
|
924
|
use YAML::XS; |
|
1
|
|
|
|
|
3670
|
|
|
1
|
|
|
|
|
316
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
# assume that we don't need to pack any disk list except our own |
128
|
|
|
|
|
|
|
sub pack_our_disk_list { |
129
|
0
|
|
|
0
|
|
|
my $self = shift; |
130
|
0
|
|
|
|
|
|
my $ip = $self->{this_ip}; |
131
|
|
|
|
|
|
|
|
132
|
0
|
|
|
|
|
|
return Dump $self->{disks_by_ip}->{$ip}; |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# unpack incoming list of lists |
136
|
|
|
|
|
|
|
sub unpack_disk_list { |
137
|
0
|
|
|
0
|
|
|
my ($self,$host,$yaml) = @_; |
138
|
0
|
|
|
|
|
|
my $ip = $self->{this_ip}; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# We shouldn't get here if the calling routine is doing its job right |
141
|
0
|
0
|
|
|
|
|
if ($host eq $ip) { |
142
|
0
|
|
|
|
|
|
warn "Fatal: caller requested unpack disk list with our IP address"; |
143
|
0
|
|
|
|
|
|
return undef; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
0
|
|
|
|
|
|
my $objref = Load $yaml; |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# Do some basic type checking on the unpacked object. We expect an |
149
|
|
|
|
|
|
|
# array of arrays. |
150
|
0
|
0
|
|
|
|
|
unless (ref($objref) eq "ARRAY") { |
151
|
0
|
|
|
|
|
|
warn "unpacked disk list is not an ARRAY"; |
152
|
0
|
|
|
|
|
|
return undef; |
153
|
|
|
|
|
|
|
|
154
|
0
|
|
|
|
|
|
for (@$objref) { |
155
|
0
|
0
|
|
|
|
|
unless (ref($_) eq "ARRAY") { |
156
|
0
|
|
|
|
|
|
warn "unpacked disk element is not an ARRAY"; |
157
|
0
|
|
|
|
|
|
return undef; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
0
|
|
|
|
|
|
$self->{update_time}->{$host} = time(); |
163
|
|
|
|
|
|
|
|
164
|
0
|
|
|
|
|
|
return $self->{disks_by_ip}->{$host} = $objref; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# |
168
|
|
|
|
|
|
|
# The remaining packages are used simply to achieve a clean separation |
169
|
|
|
|
|
|
|
# between different POE sessions and to encapsulate related methods |
170
|
|
|
|
|
|
|
# without having to worry about namespace issues (like ensuring event |
171
|
|
|
|
|
|
|
# names and handler routines are unique across all sessions). As a |
172
|
|
|
|
|
|
|
# consequence of having distinct sessions for each program area, when |
173
|
|
|
|
|
|
|
# we need to have inter-session communication, we need to use POE's |
174
|
|
|
|
|
|
|
# post method. An alias is also used to identify each of the sessions. |
175
|
|
|
|
|
|
|
# |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
## |
179
|
|
|
|
|
|
|
## The DiskWatcher package sets up a session to periodically run |
180
|
|
|
|
|
|
|
## blkid, parse the results and store them in our Info object. Since |
181
|
|
|
|
|
|
|
## blkid can sometimes hang (due to expected devices or media not |
182
|
|
|
|
|
|
|
## being present), a timer is set and if the command hasn't completed |
183
|
|
|
|
|
|
|
## within that timeout, the child process is killed and the child |
184
|
|
|
|
|
|
|
## session garbage collected. |
185
|
|
|
|
|
|
|
## |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
package Local::DiskWatcher; |
188
|
|
|
|
|
|
|
|
189
|
1
|
|
|
1
|
|
11
|
use POE qw(Wheel::Run Filter::Line); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
10
|
|
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub new { |
192
|
|
|
|
|
|
|
|
193
|
0
|
|
|
0
|
|
|
my $class = shift; |
194
|
0
|
|
|
|
|
|
my %args = ( |
195
|
|
|
|
|
|
|
program => '/sbin/blkid', |
196
|
|
|
|
|
|
|
frequency => 10 * 60 * 1, # seconds between runs |
197
|
|
|
|
|
|
|
timeout => 15, |
198
|
|
|
|
|
|
|
info => undef, |
199
|
|
|
|
|
|
|
@_ |
200
|
|
|
|
|
|
|
); |
201
|
|
|
|
|
|
|
|
202
|
0
|
0
|
|
|
|
|
die "DiskWatcher needs info => ref argument\n" unless defined($args{info}); |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# by using package_states, POE event names will eq package methods |
205
|
0
|
|
|
|
|
|
my @events = |
206
|
|
|
|
|
|
|
qw( |
207
|
|
|
|
|
|
|
_start start_child child_timeout got_child_stdout got_child_stderr |
208
|
|
|
|
|
|
|
child_cleanup |
209
|
|
|
|
|
|
|
); |
210
|
0
|
|
|
|
|
|
my $session = POE::Session->create |
211
|
|
|
|
|
|
|
( |
212
|
|
|
|
|
|
|
package_states => [$class => \@events], |
213
|
|
|
|
|
|
|
args => [%args], |
214
|
|
|
|
|
|
|
); |
215
|
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
|
return bless { session => $session }, $class; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Our _start event is solely concerned with extracting args and saving |
221
|
|
|
|
|
|
|
# them in the heap. It then queues start_child to run the actual child |
222
|
|
|
|
|
|
|
# process and timeout watcher. |
223
|
|
|
|
|
|
|
sub _start { |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
#print "DiskWatcher: _start args: ". (join ", ", @_). "\n"; |
226
|
|
|
|
|
|
|
|
227
|
0
|
|
|
0
|
|
|
my ($kernel, $heap, %args) = @_[KERNEL, HEAP, ARG0 .. $#_]; |
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
|
$heap->{timeout} = $args{timeout}; |
230
|
0
|
|
|
|
|
|
$heap->{info} = $args{info}; |
231
|
0
|
|
|
|
|
|
$heap->{program} = $args{program}; |
232
|
0
|
|
|
|
|
|
$heap->{delay} = $args{frequency}; |
233
|
0
|
|
|
|
|
|
$heap->{child} = undef; |
234
|
|
|
|
|
|
|
|
235
|
0
|
|
|
|
|
|
$kernel->yield('start_child'); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# start_child is responsible for running the program with a timeout |
239
|
|
|
|
|
|
|
sub start_child { |
240
|
0
|
|
|
0
|
|
|
my ($kernel, $heap) = @_[KERNEL, HEAP]; |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Using a named timer for timeouts. Set it to undef to deactivate. |
243
|
0
|
|
|
|
|
|
$kernel->delay(child_timeout => $heap->{timeout}); |
244
|
|
|
|
|
|
|
|
245
|
0
|
|
|
|
|
|
$heap->{child} = POE::Wheel::Run->new( |
246
|
|
|
|
|
|
|
Program => [$heap->{program}], |
247
|
|
|
|
|
|
|
StdioFilter => POE::Filter::Line->new(), |
248
|
|
|
|
|
|
|
StderrFilter => POE::Filter::Line->new(), |
249
|
|
|
|
|
|
|
StdoutEvent => "got_child_stdout", |
250
|
|
|
|
|
|
|
StderrEvent => "got_child_stderr", |
251
|
|
|
|
|
|
|
CloseEvent => "child_cleanup", |
252
|
|
|
|
|
|
|
); |
253
|
0
|
|
|
|
|
|
$kernel->sig_child($heap->{child}->PID, "child_cleanup"); |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# queue up the next run of this event |
256
|
0
|
|
|
|
|
|
$kernel->delay(start_child => $heap->{delay}); |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# if the child process didn't complete within the timeout, we kill it |
260
|
|
|
|
|
|
|
sub child_timeout { |
261
|
0
|
|
|
0
|
|
|
my ($heap) = $_[HEAP]; |
262
|
0
|
|
|
|
|
|
my $child = $heap->{child}; |
263
|
|
|
|
|
|
|
|
264
|
0
|
|
|
|
|
|
warn "CHILD KILL TIMEOUT"; |
265
|
0
|
0
|
|
|
|
|
warn "diskid failed to send kill signal\n" unless $child->kill(); |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# The kernel should eventually receive a SIGCHLD after this |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# For our purposes, we don't care whether the child exited by closing |
271
|
|
|
|
|
|
|
# its output or throwing a SIGCHLD. Wrap the deletion of references to |
272
|
|
|
|
|
|
|
# the child in if(defined()) to avoid warnings. |
273
|
|
|
|
|
|
|
sub child_cleanup { |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
#print "DiskWatcher: child_cleanup args: ". (join ", ", @_). "\n"; |
276
|
|
|
|
|
|
|
|
277
|
0
|
|
|
0
|
|
|
my ($heap,$kernel) = @_[HEAP,KERNEL]; |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Deactivate the kill timer |
280
|
0
|
|
|
|
|
|
$kernel->delay(child_timeout => undef); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# We need to commit the new list of disks and recycle the child |
283
|
|
|
|
|
|
|
# object. Both of these should only be called once, even if this |
284
|
|
|
|
|
|
|
# routine is called twice. |
285
|
0
|
0
|
|
|
|
|
if (defined($heap->{child})) { |
286
|
0
|
|
|
|
|
|
my $info = $heap->{info}; |
287
|
0
|
|
|
|
|
|
$info->commit_our_disk_info; |
288
|
|
|
|
|
|
|
|
289
|
0
|
|
|
|
|
|
delete $heap->{child}; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# Consume a single line of output (thanks to using POE::Filter::Line) |
294
|
|
|
|
|
|
|
sub got_child_stdout { |
295
|
0
|
|
|
0
|
|
|
my ($heap,$_) = @_[HEAP,ARG0]; |
296
|
|
|
|
|
|
|
|
297
|
0
|
|
|
|
|
|
my ($uuid,$label,$device) = (); |
298
|
|
|
|
|
|
|
|
299
|
0
|
0
|
|
|
|
|
$uuid = $1 if /UUID=\"([^\"]+)/; |
300
|
0
|
0
|
|
|
|
|
$label = $1 if /LABEL=\"([^\"]+)/; |
301
|
0
|
0
|
|
|
|
|
$device = $1 if /^(.*?):/; |
302
|
|
|
|
|
|
|
|
303
|
0
|
0
|
|
|
|
|
return unless defined($device); # we'll silently fail if blkid |
304
|
|
|
|
|
|
|
# output format is not as expected. |
305
|
0
|
0
|
0
|
|
|
|
return unless defined($label) or defined($uuid); |
306
|
|
|
|
|
|
|
|
307
|
0
|
|
|
|
|
|
my $info = $heap->{info}; |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# the call to add_our_disk_info just queues the update, then when we |
310
|
|
|
|
|
|
|
# clean up this child, we'll instruct info to "commit" the update. |
311
|
|
|
|
|
|
|
# This is needed to take care of removing old disks that are no |
312
|
|
|
|
|
|
|
# longer attached. |
313
|
0
|
|
|
|
|
|
$info->add_our_disk_info($uuid,$label,$device); |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# print "STDOUT: $_\n"; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
# Echo any stderr from the child |
319
|
|
|
|
|
|
|
sub got_child_stderr { |
320
|
0
|
|
|
0
|
|
|
my ($heap,$stderr,$wheel) = @_[HEAP, ARG0, ARG1]; |
321
|
0
|
|
|
|
|
|
my $child = $heap->{child}; |
322
|
0
|
|
|
|
|
|
my $pid = $child->PID; |
323
|
0
|
|
|
|
|
|
warn "blkid $pid> $stderr\n"; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
## |
327
|
|
|
|
|
|
|
## The MountWatcher package will be responsible for periodically |
328
|
|
|
|
|
|
|
## running mount to determine which of the known disks are actually |
329
|
|
|
|
|
|
|
## mounted. It will follow pretty much the same approach as for the |
330
|
|
|
|
|
|
|
## DiskWatcher package. |
331
|
|
|
|
|
|
|
## |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
package Local::MountWatcher; |
334
|
|
|
|
|
|
|
|
335
|
1
|
|
|
1
|
|
1581
|
use POE qw(Wheel::Run); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
5
|
|
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
## |
340
|
|
|
|
|
|
|
## The MulticastServer package handles connection to a multicast group |
341
|
|
|
|
|
|
|
## and sending and receving messages across it. |
342
|
|
|
|
|
|
|
## |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
package Local::MulticastServer; |
345
|
|
|
|
|
|
|
|
346
|
1
|
|
|
1
|
|
358
|
use POE; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
6
|
|
347
|
1
|
|
|
1
|
|
10566
|
use IO::Socket::Multicast; |
|
1
|
|
|
|
|
28971
|
|
|
1
|
|
|
|
|
6
|
|
348
|
|
|
|
|
|
|
|
349
|
1
|
|
|
1
|
|
917
|
use constant DATAGRAM_MAXLEN => 1500; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
75
|
|
350
|
1
|
|
|
1
|
|
23
|
use constant MCAST_PORT => 32003; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
42
|
|
351
|
1
|
|
|
1
|
|
5
|
use constant MCAST_GROUP => '230.1.2.3'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
85
|
|
352
|
1
|
|
|
1
|
|
5
|
use constant MCAST_DESTINATION => MCAST_GROUP . ':' . MCAST_PORT; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
792
|
|
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
sub new { |
355
|
|
|
|
|
|
|
|
356
|
0
|
|
|
0
|
|
|
my $class = shift; |
357
|
0
|
|
|
|
|
|
my %opts = ( |
358
|
|
|
|
|
|
|
initial_delay => 5, |
359
|
|
|
|
|
|
|
frequency => 10 * 60, |
360
|
|
|
|
|
|
|
info => undef, |
361
|
|
|
|
|
|
|
ttl => 1, # set >1 to traverse routers |
362
|
|
|
|
|
|
|
@_ |
363
|
|
|
|
|
|
|
); |
364
|
|
|
|
|
|
|
|
365
|
0
|
0
|
|
|
|
|
die "UnixSocketServer::new requires info => \$var option\n" |
366
|
|
|
|
|
|
|
unless defined($opts{info}); |
367
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
my $session = |
369
|
|
|
|
|
|
|
POE::Session->create( |
370
|
|
|
|
|
|
|
inline_states => { |
371
|
|
|
|
|
|
|
_start => \&peer_start, |
372
|
|
|
|
|
|
|
get_datagram => \&peer_read, |
373
|
|
|
|
|
|
|
send_something => \&send_something, |
374
|
|
|
|
|
|
|
}, |
375
|
|
|
|
|
|
|
heap => { |
376
|
|
|
|
|
|
|
initial_delay => $opts{initial_delay}, |
377
|
|
|
|
|
|
|
frequency => $opts{frequency}, |
378
|
|
|
|
|
|
|
info => $opts{info}, |
379
|
|
|
|
|
|
|
ttl => $opts{ttl}, |
380
|
|
|
|
|
|
|
}, |
381
|
|
|
|
|
|
|
); |
382
|
|
|
|
|
|
|
|
383
|
0
|
|
|
|
|
|
return bless { session => $session }, $class; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
# Set up the peer socket. |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
sub peer_start { |
389
|
0
|
|
|
0
|
|
|
my ($kernel,$heap) = @_[KERNEL, HEAP]; |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Don't specify an address. |
392
|
0
|
0
|
|
|
|
|
my $socket = IO::Socket::Multicast->new( |
393
|
|
|
|
|
|
|
LocalPort => MCAST_PORT, |
394
|
|
|
|
|
|
|
ReuseAddr => 1, |
395
|
|
|
|
|
|
|
#ReusePort => 1, |
396
|
|
|
|
|
|
|
) or die $!; |
397
|
|
|
|
|
|
|
|
398
|
0
|
|
|
|
|
|
$socket->mcast_ttl($heap->{ttl}); |
399
|
|
|
|
|
|
|
|
400
|
0
|
0
|
|
|
|
|
$socket->mcast_add(MCAST_GROUP) or die $!; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# Don't mcast_loopback(0). This disables multicast datagram |
403
|
|
|
|
|
|
|
# delivery to all peers on the interface. Nobody gets data. |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
# Begin watching for multicast datagrams. |
406
|
0
|
|
|
|
|
|
$kernel->select_read($socket, "get_datagram"); |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
# Save socket in the heap |
409
|
0
|
|
|
|
|
|
$heap->{socket} = $socket; |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# delay sending the first packet to give DiskWatcher a chance to complete |
412
|
0
|
|
|
|
|
|
$kernel->delay(send_something => $heap->{initial_delay}); |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
# Send something once a second. Pass the socket as a continuation. |
415
|
|
|
|
|
|
|
# $kernel->delay(send_something => $heap->{frequency}, $socket); |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# Receive a datagram when our socket sees it. |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
sub peer_read { |
421
|
0
|
|
|
0
|
|
|
my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0]; |
422
|
0
|
|
|
|
|
|
my $info = $heap->{info}; |
423
|
|
|
|
|
|
|
|
424
|
0
|
|
|
|
|
|
my $remote = recv($socket, my $message = "", DATAGRAM_MAXLEN, 0); |
425
|
|
|
|
|
|
|
|
426
|
0
|
0
|
|
|
|
|
if (defined $remote) { |
427
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
|
my ($peer_port, $peer_addr) = unpack_sockaddr_in($remote); |
429
|
0
|
|
|
|
|
|
my $ip = inet_ntoa($peer_addr); |
430
|
|
|
|
|
|
|
|
431
|
0
|
0
|
|
|
|
|
if ($message =~ s/^diskd://) { |
432
|
|
|
|
|
|
|
#print "Valid datagram received from $ip : $peer_port ... $message\n"; |
433
|
0
|
0
|
|
|
|
|
$info->unpack_disk_list($ip, $message) unless $ip eq $info->our_ip; |
434
|
|
|
|
|
|
|
} else { |
435
|
0
|
|
|
|
|
|
warn "Unexpected/malformed packet from $ip:$peer_port ... $message\n"; |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
} else { |
439
|
|
|
|
|
|
|
|
440
|
0
|
|
|
|
|
|
warn "multicast recv error (ignored) $!\n"; |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# Periodically send the list of disks |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
sub send_something { |
447
|
0
|
|
|
0
|
|
|
my ($kernel, $heap) = @_[KERNEL, HEAP]; |
448
|
0
|
|
|
|
|
|
my $info = $heap->{info}; |
449
|
0
|
|
|
|
|
|
my $socket = $heap->{socket}; |
450
|
0
|
|
|
|
|
|
my $delay = $heap->{frequency}; |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# my $message = "pid $$ sending at " . time() . " to " . MCAST_DESTINATION; |
453
|
0
|
|
|
|
|
|
my $message = "diskd:" . $info->pack_our_disk_list; |
454
|
|
|
|
|
|
|
|
455
|
0
|
0
|
|
|
|
|
warn $! unless $socket->mcast_send($message, MCAST_DESTINATION); |
456
|
|
|
|
|
|
|
|
457
|
0
|
|
|
|
|
|
$kernel->delay(send_something => $delay); |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
## |
462
|
|
|
|
|
|
|
## The UnixSocketServer package uses a Unix domain socket to provide a |
463
|
|
|
|
|
|
|
## local ineterface to the disk info and a means of sending commands |
464
|
|
|
|
|
|
|
## or messages to other nodes in our multicast network. |
465
|
|
|
|
|
|
|
## |
466
|
|
|
|
|
|
|
## This package comprises a main server package (UnixSocketServer) |
467
|
|
|
|
|
|
|
## that waits for connections to the socket, and and a package that's |
468
|
|
|
|
|
|
|
## spawned for each incoming connection (UnixSocketServer::Session). |
469
|
|
|
|
|
|
|
## |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
package Local::UnixSocketServer; |
472
|
|
|
|
|
|
|
|
473
|
1
|
|
|
1
|
|
7
|
use POE qw(Wheel::SocketFactory Wheel::ReadWrite); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
8
|
|
474
|
1
|
|
|
1
|
|
21204
|
use Socket; # For PF_UNIX. |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1112
|
|
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# Start server at a particular rendezvous (ie, Unix domain socket) |
477
|
|
|
|
|
|
|
sub new { |
478
|
0
|
|
|
0
|
|
|
my $class = shift; |
479
|
0
|
|
|
|
|
|
my $homedir = $ENV{HOME}; |
480
|
0
|
|
|
|
|
|
my %opts = |
481
|
|
|
|
|
|
|
( |
482
|
|
|
|
|
|
|
rendezvous => "$homedir/.diskd-socket", |
483
|
|
|
|
|
|
|
info => undef, |
484
|
|
|
|
|
|
|
@_, |
485
|
|
|
|
|
|
|
); |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
# warn "class: $class; opts: " . (join ", ", @_); |
488
|
|
|
|
|
|
|
|
489
|
0
|
0
|
|
|
|
|
die "UnixSocketServer::new requires info => \$var option\n" |
490
|
|
|
|
|
|
|
unless defined($opts{info}); |
491
|
|
|
|
|
|
|
|
492
|
0
|
|
|
|
|
|
POE::Session->create( |
493
|
|
|
|
|
|
|
inline_states => { |
494
|
|
|
|
|
|
|
_start => \&server_started, |
495
|
|
|
|
|
|
|
got_client => \&server_accepted, |
496
|
|
|
|
|
|
|
got_error => \&server_error, |
497
|
|
|
|
|
|
|
}, |
498
|
|
|
|
|
|
|
heap => { |
499
|
|
|
|
|
|
|
rendezvous => $opts{rendezvous}, |
500
|
|
|
|
|
|
|
info => $opts{info} |
501
|
|
|
|
|
|
|
}, |
502
|
|
|
|
|
|
|
); |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# The server session has started. Create a socket factory that |
506
|
|
|
|
|
|
|
# listens for UNIX socket connections and returns connected sockets. |
507
|
|
|
|
|
|
|
# This unlinks the rendezvous socket |
508
|
|
|
|
|
|
|
sub server_started { |
509
|
0
|
|
|
0
|
|
|
my ($kernel, $heap) = @_[KERNEL, HEAP]; |
510
|
0
|
0
|
|
|
|
|
unlink $heap->{rendezvous} if -e $heap->{rendezvous}; |
511
|
0
|
|
|
|
|
|
$heap->{server} = POE::Wheel::SocketFactory->new( |
512
|
|
|
|
|
|
|
SocketDomain => PF_UNIX, |
513
|
|
|
|
|
|
|
BindAddress => $heap->{rendezvous}, |
514
|
|
|
|
|
|
|
SuccessEvent => 'got_client', |
515
|
|
|
|
|
|
|
FailureEvent => 'got_error', |
516
|
|
|
|
|
|
|
); |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# The server encountered an error while setting up or perhaps while |
520
|
|
|
|
|
|
|
# accepting a connection. Register the error and shut down the server |
521
|
|
|
|
|
|
|
# socket. This will not end the program until all clients have |
522
|
|
|
|
|
|
|
# disconnected, but it will prevent the server from receiving new |
523
|
|
|
|
|
|
|
# connections. |
524
|
|
|
|
|
|
|
sub server_error { |
525
|
0
|
|
|
0
|
|
|
my ($heap, $syscall, $errno, $error) = @_[HEAP, ARG0 .. ARG2]; |
526
|
0
|
0
|
|
|
|
|
$error = "Normal client disconnection." unless $errno; |
527
|
0
|
|
|
|
|
|
warn "Server socket encountered $syscall error $errno: $error\n"; |
528
|
0
|
|
|
|
|
|
delete $heap->{server}; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
# The server accepted a connection. Start another session to process |
532
|
|
|
|
|
|
|
# data on it. |
533
|
|
|
|
|
|
|
sub server_accepted { |
534
|
0
|
|
|
0
|
|
|
my ($heap,$client_socket) = @_[HEAP, ARG0]; |
535
|
0
|
|
|
|
|
|
my $info = $heap->{info}; |
536
|
0
|
|
|
|
|
|
Local::UnixSocketServer::Session->new($client_socket, $info); |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
## A UnixSocketServer::Session instance is created for each incoming |
540
|
|
|
|
|
|
|
## connection. |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
package Local::UnixSocketServer::Session; |
543
|
|
|
|
|
|
|
|
544
|
1
|
|
|
1
|
|
8
|
use POE::Session; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
7
|
|
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
# Constructor |
547
|
|
|
|
|
|
|
sub new { |
548
|
0
|
|
|
0
|
|
|
my ($class,$socket,$info) = @_; |
549
|
|
|
|
|
|
|
#warn "new $class: $socket, $info"; |
550
|
0
|
|
|
|
|
|
POE::Session->create( |
551
|
|
|
|
|
|
|
package_states => [ $class => [qw( _start session_input session_error)] ], |
552
|
|
|
|
|
|
|
args => [$info, $socket], |
553
|
|
|
|
|
|
|
); |
554
|
|
|
|
|
|
|
} |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# The server session has started. Wrap the socket it's been given in |
557
|
|
|
|
|
|
|
# a ReadWrite wheel. ReadWrite handles the tedious task of performing |
558
|
|
|
|
|
|
|
# buffered reading and writing on an unbuffered socket. |
559
|
|
|
|
|
|
|
sub _start { |
560
|
0
|
|
|
0
|
|
|
my ($heap, $info, $socket) = @_[HEAP, ARG0, ARG1]; |
561
|
0
|
|
|
|
|
|
$heap->{client} = POE::Wheel::ReadWrite->new( |
562
|
|
|
|
|
|
|
Handle => $socket, |
563
|
|
|
|
|
|
|
InputEvent => 'session_input', |
564
|
|
|
|
|
|
|
ErrorEvent => 'session_error', |
565
|
|
|
|
|
|
|
# InputEvent => 'got_client_input', |
566
|
|
|
|
|
|
|
# ErrorEvent => 'got_client_error', |
567
|
|
|
|
|
|
|
); |
568
|
0
|
|
|
|
|
|
$heap->{info}=$info; |
569
|
0
|
|
|
|
|
|
$heap->{client}->put("diskd local interface awaiting commands\n"); |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
# The server session received some input from its attached client. |
573
|
|
|
|
|
|
|
# Echo it back. |
574
|
|
|
|
|
|
|
sub session_input { |
575
|
0
|
|
|
0
|
|
|
my ($heap, $_) = @_[HEAP, ARG0]; |
576
|
0
|
|
|
|
|
|
my $info = $heap->{info}; |
577
|
|
|
|
|
|
|
|
578
|
0
|
|
|
|
|
|
chomp; |
579
|
|
|
|
|
|
|
|
580
|
0
|
0
|
|
|
|
|
if (/^help\b/i) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
581
|
0
|
|
|
|
|
|
$heap->{client}->put |
582
|
|
|
|
|
|
|
("Available commands:\n" . |
583
|
|
|
|
|
|
|
"list show disk info\n" . |
584
|
|
|
|
|
|
|
"where |
585
|
|
|
|
|
|
|
"localhost report local hostname, IP address\n" . |
586
|
|
|
|
|
|
|
"status show network statistics\n" . |
587
|
|
|
|
|
|
|
"debug start monitoring notable events\n" . |
588
|
|
|
|
|
|
|
"quit|exit exit client" # handled client-side |
589
|
|
|
|
|
|
|
); |
590
|
|
|
|
|
|
|
} elsif (/^list\b/i) { |
591
|
|
|
|
|
|
|
|
592
|
0
|
|
|
|
|
|
my $output = ''; |
593
|
0
|
|
|
|
|
|
foreach my $host ($info->known_hosts) { |
594
|
|
|
|
|
|
|
#warn "Got host $host"; |
595
|
0
|
|
|
|
|
|
foreach my $listref (@{$info->disks_by_host($host)}) { |
|
0
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
# Perl lets us use hash slices as well as array slices |
597
|
0
|
|
|
|
|
|
my ($uuid, $label, $device) = @$listref; |
598
|
0
|
0
|
|
|
|
|
$uuid = '' unless defined $uuid; |
599
|
0
|
0
|
|
|
|
|
$label = '' unless defined $label; |
600
|
0
|
0
|
|
|
|
|
$device = '' unless defined $device; |
601
|
|
|
|
|
|
|
|
602
|
0
|
|
|
|
|
|
$output.= sprintf("%-15s %-37s %-10s %s\n", |
603
|
|
|
|
|
|
|
"$host:",$uuid,$label,$device); |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
} |
606
|
0
|
|
|
|
|
|
$heap->{client}->put($output); |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
} elsif (/^where\b/i) { |
609
|
0
|
0
|
|
|
|
|
if (/^where\b\s+(\S+)/i) { |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
} else { |
612
|
0
|
|
|
|
|
|
$heap->{client}->put("'where' requires a disk label or uuid\n"); |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
} elsif (/^localhost\b/i) { |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
} elsif (/^status\b/i) { |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
} elsif (/^debug\b/i) { |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
} else { |
621
|
0
|
|
|
|
|
|
$heap->{client}->put("unknown command: $_\n"); |
622
|
|
|
|
|
|
|
} |
623
|
|
|
|
|
|
|
} |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
# The server session received an error from the client socket. Log |
626
|
|
|
|
|
|
|
# the error and shut down this session. The main server remains |
627
|
|
|
|
|
|
|
# untouched by this. |
628
|
|
|
|
|
|
|
sub session_error { |
629
|
0
|
|
|
0
|
|
|
my ($heap, $syscall, $errno, $error) = @_[HEAP, ARG0 .. ARG2]; |
630
|
0
|
0
|
|
|
|
|
$error = "Normal disconnection." unless $errno; |
631
|
0
|
|
|
|
|
|
warn "Server session encountered $syscall error $errno: $error\n"; |
632
|
0
|
|
|
|
|
|
delete $heap->{client}; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
package Local::UnixSocketClient; |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
# This program is a simple unix socket client. It will connect to the |
639
|
|
|
|
|
|
|
# UNIX socket specified by $rendezvous. This program is written to |
640
|
|
|
|
|
|
|
# work with the UnixServer example in POE's cookbook. While it |
641
|
|
|
|
|
|
|
# touches upon several POE modules, it is not meant to be an |
642
|
|
|
|
|
|
|
# exhaustive example of them. Please consult "perldoc [module]" for |
643
|
|
|
|
|
|
|
# more details. |
644
|
|
|
|
|
|
|
|
645
|
1
|
|
|
1
|
|
1029
|
use Socket qw(AF_UNIX); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
55
|
|
646
|
1
|
|
|
1
|
|
7
|
use POE; # For base features. |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
647
|
1
|
|
|
1
|
|
451
|
use POE::Wheel::SocketFactory; # To create sockets. |
|
1
|
|
|
|
|
10
|
|
|
1
|
|
|
|
|
25
|
|
648
|
1
|
|
|
1
|
|
6
|
use POE::Wheel::ReadWrite; # To read/write lines with sockets. |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
20
|
|
649
|
1
|
|
|
1
|
|
2835
|
use POE::Wheel::ReadLine; # To read/write lines on the console. |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
# Specify a UNIX rendezvous to use. This is the location the client |
652
|
|
|
|
|
|
|
# will connect to, and it should correspond to the location a server |
653
|
|
|
|
|
|
|
# is listening to. |
654
|
|
|
|
|
|
|
our $rendezvous; |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
sub new { |
657
|
|
|
|
|
|
|
my $class = shift; |
658
|
|
|
|
|
|
|
my $homedir = $ENV{HOME}; |
659
|
|
|
|
|
|
|
my %opts = |
660
|
|
|
|
|
|
|
( |
661
|
|
|
|
|
|
|
rendezvous => "$homedir/.diskd-socket", |
662
|
|
|
|
|
|
|
@_, |
663
|
|
|
|
|
|
|
); |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
$rendezvous = $opts{rendezvous}; |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# Create the session that will pass information between the console |
668
|
|
|
|
|
|
|
# and the server. The create() constructor maps a number of events |
669
|
|
|
|
|
|
|
# to the functions that will be called to handle them. For example, |
670
|
|
|
|
|
|
|
# the "sock_connected" event will cause the socket_connected() |
671
|
|
|
|
|
|
|
# function to be called. |
672
|
|
|
|
|
|
|
POE::Session->create( |
673
|
|
|
|
|
|
|
inline_states => { |
674
|
|
|
|
|
|
|
_start => \&client_init, |
675
|
|
|
|
|
|
|
sock_connected => \&socket_connected, |
676
|
|
|
|
|
|
|
sock_error => \&socket_error, |
677
|
|
|
|
|
|
|
sock_input => \&socket_input, |
678
|
|
|
|
|
|
|
cli_input => \&console_input, |
679
|
|
|
|
|
|
|
}, |
680
|
|
|
|
|
|
|
); |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
# The client_init() function is called when POE sends a "_start" event |
684
|
|
|
|
|
|
|
# to the session. This happens automatically whenever a session is |
685
|
|
|
|
|
|
|
# created, and its purpose is to notify your code when it can begin |
686
|
|
|
|
|
|
|
# doing things. |
687
|
|
|
|
|
|
|
# Here we create the SocketFactory that will connect a socket to the |
688
|
|
|
|
|
|
|
# server. The socket factory is tightly associated with its session, |
689
|
|
|
|
|
|
|
# so it is kept in the session's private storage space (its "heap"). |
690
|
|
|
|
|
|
|
# The socket factory is configured to emit two events: On a successful |
691
|
|
|
|
|
|
|
# connection, it sends a "sock_connected" event containing the new |
692
|
|
|
|
|
|
|
# socket. On a failure, it sends "sock_error" along with information |
693
|
|
|
|
|
|
|
# about the problem. |
694
|
|
|
|
|
|
|
sub client_init { |
695
|
|
|
|
|
|
|
my $heap = $_[HEAP]; |
696
|
|
|
|
|
|
|
$heap->{connect_wheel} = POE::Wheel::SocketFactory->new( |
697
|
|
|
|
|
|
|
SocketDomain => AF_UNIX, |
698
|
|
|
|
|
|
|
RemoteAddress => $rendezvous, |
699
|
|
|
|
|
|
|
SuccessEvent => 'sock_connected', |
700
|
|
|
|
|
|
|
FailureEvent => 'sock_error', |
701
|
|
|
|
|
|
|
); |
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
# socket_connected() is called when the session receives a |
705
|
|
|
|
|
|
|
# "sock_connected" event. That event is generated by the session's |
706
|
|
|
|
|
|
|
# SocketFactory object when it has connected to a server. The newly |
707
|
|
|
|
|
|
|
# connected socket is passed in ARG0. |
708
|
|
|
|
|
|
|
# This function discards the SocketFactory object since its purpose |
709
|
|
|
|
|
|
|
# has been fulfilled. It then creates two new objects: a ReadWrite |
710
|
|
|
|
|
|
|
# wheel to talk with the socket, and a ReadLine wheel to talk with the |
711
|
|
|
|
|
|
|
# console. POE::Wheel::ReadLine was named after Term::ReadLine, by |
712
|
|
|
|
|
|
|
# the way. Once socket_connected() has set us up the wheels, it calls |
713
|
|
|
|
|
|
|
# ReadLine's get() method to prompt the user for input. |
714
|
|
|
|
|
|
|
sub socket_connected { |
715
|
|
|
|
|
|
|
my ($heap, $socket) = @_[HEAP, ARG0]; |
716
|
|
|
|
|
|
|
delete $heap->{connect_wheel}; |
717
|
|
|
|
|
|
|
$heap->{io_wheel} = POE::Wheel::ReadWrite->new( |
718
|
|
|
|
|
|
|
Handle => $socket, |
719
|
|
|
|
|
|
|
InputEvent => 'sock_input', |
720
|
|
|
|
|
|
|
ErrorEvent => 'sock_error', |
721
|
|
|
|
|
|
|
); |
722
|
|
|
|
|
|
|
$heap->{cli_wheel} = POE::Wheel::ReadLine->new(InputEvent => 'cli_input'); |
723
|
|
|
|
|
|
|
$heap->{cli_wheel}->get("=> "); |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
# socket_input() is called to handle "sock_input" events. These |
727
|
|
|
|
|
|
|
# events are provided by the POE::Wheel::ReadWrite object that was |
728
|
|
|
|
|
|
|
# created in socket_connected(). |
729
|
|
|
|
|
|
|
# socket_input() moves information from the socket to the console. |
730
|
|
|
|
|
|
|
sub socket_input { |
731
|
|
|
|
|
|
|
my ($heap, $input) = @_[HEAP, ARG0]; |
732
|
|
|
|
|
|
|
$heap->{cli_wheel}->put("$input"); |
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
# socket_error() is called to handle "sock_error" events. These |
736
|
|
|
|
|
|
|
# events can come from two places: The SocketFactory will send it if a |
737
|
|
|
|
|
|
|
# connection fails, and the ReadWrite object will send it if a read or |
738
|
|
|
|
|
|
|
# write error occurs. |
739
|
|
|
|
|
|
|
# The most common way to handle I/O errors is to shut down the sockets |
740
|
|
|
|
|
|
|
# having problems. Here we'll delete all our wheels so the program |
741
|
|
|
|
|
|
|
# can shut down gracefully. |
742
|
|
|
|
|
|
|
# ARG0 contains the name of the syscall that failed. It is often |
743
|
|
|
|
|
|
|
# "connect" or "bind" or "read" or "write". ARG1 and ARG2 contain the |
744
|
|
|
|
|
|
|
# numeric and descriptive contents of $! at the time of the failure. |
745
|
|
|
|
|
|
|
sub socket_error { |
746
|
|
|
|
|
|
|
my ($heap, $syscall, $errno, $error) = @_[HEAP, ARG0 .. ARG2]; |
747
|
|
|
|
|
|
|
$error = "Normal disconnection." unless $errno; |
748
|
|
|
|
|
|
|
warn "Client socket encountered $syscall error $errno: $error"; |
749
|
|
|
|
|
|
|
delete $heap->{connect_wheel}; |
750
|
|
|
|
|
|
|
delete $heap->{io_wheel}; |
751
|
|
|
|
|
|
|
delete $heap->{cli_wheel}; |
752
|
|
|
|
|
|
|
} |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# Finally, the console_input() function is called to handle |
755
|
|
|
|
|
|
|
# "cli_input" events. These events are created when |
756
|
|
|
|
|
|
|
# POE::Wheel::ReadLine (created in socket_connected()) receives user |
757
|
|
|
|
|
|
|
# input from the console. |
758
|
|
|
|
|
|
|
# Plain input is registered with ReadLine's input history, echoed back |
759
|
|
|
|
|
|
|
# to the console, and sent to the server. Exceptions, such as when |
760
|
|
|
|
|
|
|
# the user presses Ctrl+C to interrupt the program, are also handled. |
761
|
|
|
|
|
|
|
# POE::Wheel::ReadLine events include two parameters other than the |
762
|
|
|
|
|
|
|
# usual KERNEL, HEAP, etc. The ARG0 parameter contains plain input. |
763
|
|
|
|
|
|
|
# If that's undefined, then ARG1 will contain an exception. |
764
|
|
|
|
|
|
|
sub console_input { |
765
|
|
|
|
|
|
|
my ($heap, $input, $exception) = @_[HEAP, ARG0, ARG1]; |
766
|
|
|
|
|
|
|
if (defined $input) { |
767
|
|
|
|
|
|
|
$heap->{cli_wheel}->addhistory($input); |
768
|
|
|
|
|
|
|
# $heap->{cli_wheel}->put("You Said: $input"); |
769
|
|
|
|
|
|
|
$heap->{io_wheel}->put($input); |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
elsif ($exception eq 'cancel') { |
772
|
|
|
|
|
|
|
$heap->{cli_wheel}->put("Canceled."); |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
else { |
775
|
|
|
|
|
|
|
$heap->{cli_wheel}->put("Bye."); |
776
|
|
|
|
|
|
|
delete $heap->{cli_wheel}; |
777
|
|
|
|
|
|
|
delete $heap->{io_wheel}; |
778
|
|
|
|
|
|
|
return; |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
# Prompt for the next bit of input. |
782
|
|
|
|
|
|
|
$heap->{cli_wheel}->get("=> "); |
783
|
|
|
|
|
|
|
} |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
1; |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
__END__ |