use $server::new_clients tie
[pxelator] / lib / PXElator / client.pm
1 package client;
2
3 use warnings;
4 use strict;
5 use autodie;
6
7 use File::Slurp;
8 use Data::Dump qw/dump/;
9 use File::Path;
10
11 use server;
12 use format;
13 use ip;
14 use ping;
15
16 our $debug = $server::debug;
17
18 sub mkbasedir {
19         my $path = shift;
20         $path =~ s{(^.*)/[^/]+$}{$1};
21         mkdir $path unless -d $path;
22         return $path;
23 }
24
25 sub mac_path { $server::conf . '/mac/' . $_[0] }
26 sub  ip_path { $server::conf . '/ip/'  . join('/', @_) }
27 sub conf_value {
28         my $path = shift;
29         my $value;
30         if ( -l $path ) {
31                 $value = readlink $path;
32                 $value =~ s{.*/([^/]+)$}{$1};
33         } elsif ( -f $path ) {
34                 $value = read_file $path;
35         } else {
36                 warn "W: $path not file or symlink\n";
37         }
38         return $value;
39 }
40
41 sub conf {
42         my $ip  = shift;
43         my $name = shift;
44         my ( $default, $value );
45         if ( $#_ == 0 ) {
46                 $value = shift;
47         } elsif ( $#_ == 1 && $_[0] eq 'default' ) {
48                 $default = $_[1]
49         }
50
51         my $path = ip_path $ip;
52         mkdir $path unless -d $path;
53         $path .= '/' . $name;
54
55         if ( defined $value ) {
56                 mkbasedir  $path;
57                 write_file $path, $value;
58                 warn "update $path = $value";
59         } elsif ( ! -e $path && defined $default ) {
60                 mkbasedir  $path;
61                 write_file $path, $default;
62                 warn "default $path = $default";
63                 $value = $default;
64         } elsif ( -f $path ) {
65                 $value = read_file $path;
66         } else {
67                 warn "# $name missing $path\n" if $debug;
68         }
69         return $value;
70 }
71
72 sub all_conf {
73         my $ip = shift;
74         my $path = ip_path $ip || return;
75         my $conf;
76         foreach my $file ( glob("$path/*") ) {
77                 my $name = $file;
78                 $name =~ s{^.+/([^/]+)$}{$1};
79                 $conf->{ $name } = read_file $file;
80         }
81         return $conf;
82 }
83 sub next_ip($) {
84         my $mac = shift;
85         $mac = format::mac($mac);
86
87         if ( $server::new_clients-- ) {
88                 warn "# clients left: $server::new_clients\n";
89         } else {
90                 warn "W: no new clients accepted";
91                 return '0.0.0.0';
92         }
93
94         my $prefix = $server::ip;
95         $prefix =~ s{\.\d+$}{.};
96         my $addr = $server::ip_from || die;
97         my $ip = $prefix . $addr;
98
99         while ( -e ip_path($ip) || ping::host($ip) ) {
100                 $ip = $prefix . $addr++;
101                 die "all addresses allocated!" if $addr == $server::ip_to;
102                 warn "skip $ip\n";
103         }
104
105         warn "next_ip $ip\n";
106
107         save_ip_mac( $ip, $mac );
108
109         return $ip;
110 }
111
112 sub save_ip_mac {
113         my ($ip,$mac) = @_;
114         $mac = format::mac($mac);
115         return if $mac eq '00:00:00:00:00:00';
116
117         mkdir ip_path($ip) unless -e ip_path($ip);
118
119         my $mac_path = mac_path($mac);
120         unlink $mac_path if -l $mac_path;       # XXX audit?
121         symlink ip_path($ip), $mac_path;
122         write_file( ip_path($ip,'mac'), $mac );
123 }
124
125 sub ip_from_mac($) {
126         my $mac = shift;
127         $mac = format::mac($mac);
128
129         my $mac_path = mac_path $mac;
130         return unless -e $mac_path;
131
132         my $ip;
133
134         if ( -f $mac_path ) {
135                 $ip = read_file $mac_path;
136                 unlink $mac_path;
137                 symlink ip_path($ip), $mac_path;
138                 warn "I: upgrade to mac symlink $mac_path\n";
139         } elsif ( -l $mac_path ) {
140                 $ip = conf_value $mac_path;
141         } else {
142                 die "$mac_path not file or symlink";
143         }
144
145         return $ip;
146 }
147
148 sub mac_from_ip($) {
149         my $ip = shift;
150         conf_value ip_path($ip, 'mac');
151 }
152
153 sub change_ip($$) {
154         my ($old, $new) = @_;
155         return if $old eq $new;
156         my $mac = mac_from_ip($old) || die "no mac for $old";
157         rename ip_path($old), ip_path($new);
158         unlink mac_path($mac);
159         symlink ip_path($new), mac_path($mac);
160         return $new;
161 }
162
163 sub all_ips {
164         sort { ip::to_int($a) cmp ip::to_int($b) }
165         map {
166                 my $ip = $_;
167                 $ip =~ s{^.+/ip/}{};
168                 $ip;
169         } glob("$server::conf/ip/*") 
170 }
171
172 sub remove {
173         my $ip = shift;
174         unlink $_ foreach grep { -e $_ } ( glob "$server::conf/ip/$ip/*" );
175         if ( my $mac = mac_from_ip $ip ) {
176                 unlink "$server::conf/mac/$mac";
177         }
178         rmtree "$server::conf/ip/$ip";
179 }
180
181 sub arp_mac_dev {
182         my $arp = {
183                 map {
184                         my @c = split(/\s+/,$_);
185                         if ( $#c == 5 ) {
186                                 client::save_ip_mac( $c[0], $c[3] );
187                                 ( uc $c[3] => $c[5] )
188                         } else {
189                         }
190                 } read_file('/proc/net/arp')
191         };
192
193         warn "# arp ",dump( $arp );
194         return $arp;
195 }
196
197 1;