warn about files in ip/ dir
[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 use kvm;
16
17 our $debug = $server::debug;
18
19 sub mkbasedir {
20         my $path = shift;
21         $path =~ s{(^.*)/[^/]+$}{$1};
22         mkdir $path unless -d $path;
23         return $path;
24 }
25
26 sub mac_path { $server::conf . '/mac/' . $_[0] }
27 sub  ip_path { $server::conf . '/ip/'  . join('/', @_) }
28 sub conf_value {
29         my $path = shift;
30         my $value;
31         if ( -l $path ) {
32                 $value = readlink $path;
33                 $value =~ s{.*/([^/]+)$}{$1};
34         } elsif ( -f $path ) {
35                 $value = read_file $path;
36         } else {
37                 warn "W: $path not file or symlink\n";
38         }
39         return $value;
40 }
41
42 sub conf {
43         my $ip  = shift;
44         my $name = shift;
45         my ( $default, $value );
46         if ( $#_ == 0 ) {
47                 $value = shift;
48         } elsif ( $#_ == 1 && $_[0] eq 'default' ) {
49                 $default = $_[1]
50         }
51
52         my $path = ip_path $ip;
53         mkdir $path unless -e $path;
54         warn "WARNING: $path not directory" unless -d $path;
55         $path .= '/' . $name;
56
57         if ( defined $value ) {
58                 mkbasedir  $path;
59                 write_file $path, $value;
60                 warn "update $path = $value";
61         } elsif ( ! -e $path && defined $default ) {
62                 mkbasedir  $path;
63                 write_file $path, $default;
64                 warn "default $path = $default";
65                 $value = $default;
66         } elsif ( -l $path ) {
67                 $value = readlink $path;
68         } elsif ( -f $path ) {
69                 $value = read_file $path;
70                 chomp $value;
71         } else {
72                 warn "# $name missing $path\n" if $debug;
73         }
74         return $value;
75 }
76
77 sub all_conf {
78         my $ip = shift;
79         my $path = ip_path $ip || return;
80         my $conf;
81         foreach my $file ( glob("$path/*"), glob("$path/*/*") ) {
82                 my $name = $file;
83                 $name =~ s{^$path/+}{} || die "can't remove $path from $name";
84                 $conf->{ $name } =
85                         -l $file ? readlink  $file :
86                         -f $file ? read_file $file :
87                         '?';
88         }
89         return $conf;
90 }
91 sub next_ip($) {
92         my $mac = shift;
93         $mac = format::mac($mac);
94
95         if ( $server::new_clients > 0 ) {
96                 warn "# clients left: ", --$server::new_clients;
97         } else {
98                 warn "W: no new clients accepted";
99                 return '0.0.0.0';
100         }
101
102         my $prefix = $server::ip;
103         $prefix =~ s{\.\d+$}{.};
104         my $addr = $server::ip_from || die;
105         my $ip = $prefix . $addr;
106
107         while ( -e ip_path($ip) || ping::host($ip) ) {
108                 $ip = $prefix . $addr++;
109                 die "all addresses allocated!" if $addr == $server::ip_to;
110                 warn "skip $ip\n";
111         }
112
113         warn "next_ip $ip\n";
114
115         save_ip_mac( $ip, $mac );
116
117         return $ip;
118 }
119
120 sub save_ip_mac {
121         my ($ip,$mac) = @_;
122         $mac = format::mac($mac);
123         return if $mac eq '00:00:00:00:00:00' || $ip eq '0.0.0.0';
124
125         mkdir ip_path($ip) unless -e ip_path($ip);
126
127         my $mac_path = mac_path($mac);
128         unlink $mac_path if -l $mac_path;       # XXX audit?
129         symlink ip_path($ip), $mac_path;
130         write_file( ip_path($ip,'mac'), $mac );
131 }
132
133 sub ip_from_mac($) {
134         my $mac = shift;
135         $mac = format::mac($mac);
136
137         my $mac_path = mac_path $mac;
138         return unless -e $mac_path;
139
140         my $ip;
141
142         if ( -f $mac_path ) {
143                 $ip = read_file $mac_path;
144                 unlink $mac_path;
145                 symlink ip_path($ip), $mac_path;
146                 warn "I: upgrade to mac symlink $mac_path\n";
147         } elsif ( -l $mac_path ) {
148                 $ip = conf_value $mac_path;
149         } else {
150                 die "$mac_path not file or symlink";
151         }
152
153         return $ip;
154 }
155
156 sub mac_from_ip($) {
157         my $ip = shift;
158         conf_value ip_path($ip, 'mac');
159 }
160
161 sub change_ip($$) {
162         my ($old, $new) = @_;
163         return if $old eq $new;
164         my $mac = mac_from_ip($old) || die "no mac for $old";
165         rename ip_path($old), ip_path($new);
166         unlink mac_path($mac);
167         symlink ip_path($new), mac_path($mac);
168         return $new;
169 }
170
171 sub all_ips {
172         sort { ip::to_int($a) cmp ip::to_int($b) }
173         map {
174                 my $ip = $_;
175                 $ip =~ s{^.+/ip/}{};
176                 autocreate_params( $ip );
177                 $ip;
178         } glob("$server::conf/ip/*") 
179 }
180
181 sub remove {
182         my $ip = shift;
183         if ( my $mac = mac_from_ip $ip ) {
184                 unlink "$server::conf/mac/$mac";
185         }
186         rmtree "$server::conf/ip/$ip";
187 }
188
189 sub arp_mac_dev {
190         my $arp = {
191                 map {
192                         my @c = split(/\s+/,$_);
193                         if ( $#c == 5 ) {
194                                 client::save_ip_mac( $c[0], $c[3] );
195                                 ( uc $c[3] => $c[5] )
196                         } else {
197                         }
198                 } read_file('/proc/net/arp')
199         };
200
201         warn "# arp ",dump( $arp );
202         return $arp;
203 }
204
205 sub rebuild_mac_links {
206         warn "# rebuild mac links";
207         foreach my $ip ( all_ips ) {
208                 my $mac = ip_path $ip, 'mac';
209                 if ( -e $mac ) {
210                         $mac = read_file $mac;
211                         chomp $mac;
212                         save_ip_mac( $ip, $mac );
213                         warn "## $ip $mac\n";
214                 }
215         }
216 }
217
218 sub autocreate_params {
219         my $ip = shift;
220         my $mac = mac_from_ip $ip;
221         if ( $mac =~ m{^AC:DE:48:00:00} && ! defined conf( $ip, 'kvm' ) ) {
222                 conf( $ip, 'kvm', default => kvm::nr_from_mac( $mac ) );
223                 warn "# create kvm for $ip";
224         }
225 }
226
227 1;