11 use Storable
qw(dclone);
15 # Current config, r() parses a network interface string into this variable
21 # perl conveniently lets you open a string as filehandle so we allow tests
22 # to temporarily save interface files to virtual files:
25 # Load a temp-file and return it as a string, if it didn't exist, try loading
30 if (my $local = $saved_files{$from}) {
34 open my $fh, '<', $from or die "failed to open $from: $!";
41 # Save a temporary file.
43 my ($file, $data) = @_;
44 $saved_files{$file} = $data;
47 # Delete a temporary file
50 die "no such file: $file" if !delete $saved_files{$file};
53 # Delete all temporary files.
55 foreach (keys %saved_files) {
56 delete $saved_files{$_} if $_ !~ m
,^shared
/,;
64 # Read an interfaces file with optional /proc/net/dev file content string and
65 # the list of active interfaces, which otherwise default
67 my ($ifaces, $proc_net_dev, $active) = @_;
68 $proc_net_dev //= load
('proc_net_dev');
69 $active //= [split(/\s+/, load
('active_interfaces'))];
70 open my $fh1, '<', \
$ifaces;
71 open my $fh2, '<', \
$proc_net_dev;
72 $config = PVE
::INotify
::__read_etc_network_interfaces
($fh1, $fh2, $active);
76 # Turn the current network config into a string.
78 # write shouldn't be able to change a previously parsed config
79 my $config_clone = dclone
($config);
80 return PVE
::INotify
::__write_etc_network_interfaces
($config_clone);
84 ## Interface modification helpers
88 sub update_iface
($$%) {
89 my ($name, $families, %extra) = @_;
91 my $ifaces = $config->{ifaces
};
92 my $if = $ifaces->{$name};
94 die "no such interface: $name\n" if !$if;
98 # merge extra flags (like bridge_ports, ovs_*) directly
99 $if->{$_} = $extra{$_} foreach keys %extra;
101 return if !$families;
103 my $if_families = $if->{families
} ||= [];
104 foreach my $family (@$families) {
105 my $type = delete $family->{family
};
106 @$if_families = ((grep { $_ ne $type } @$if_families), $type);
108 (my $suffix = $type) =~ s/^inet//;
109 $if->{"method$suffix"} = $family->{address
} ?
'static' : 'manual';
110 foreach(qw(address netmask gateway options)) {
111 if (my $value = delete $family->{$_}) {
112 $if->{"$_${suffix}"} = $value;
118 # Create an interface and error if it already exists.
119 sub new_iface
($$$%) {
120 my ($name, $type, $families, %extra) = @_;
121 my $ifaces = $config->{ifaces
};
122 croak
"interface already exists: $name" if $ifaces->{$name};
123 $ifaces->{$name} = { type
=> $type };
124 update_iface
($name, $families, %extra);
127 # Delete an interface and error if it did not exist.
128 sub delete_iface
($;$) {
129 my ($name, $family) = @_;
130 my $ifaces = $config->{ifaces
};
131 my $if = $ifaces->{$name} ||= {};
132 croak
"interface doesn't exist: $name" if !$if;
135 delete $ifaces->{$name};
139 my $families = $if->{families
};
140 @$families = grep {$_ ne $family} @$families;
141 (my $suffix = $family) =~ s/^inet//;
142 delete $if->{"$_$suffix"} foreach qw(address netmask gateway options);
149 # Compare two strings line by line and show a diff/error if they differ.
154 my ($ra, $wa) = POSIX
::pipe();
155 my ($rb, $wb) = POSIX
::pipe();
156 my $ha = IO
::Handle-
>new_from_fd($wa, 'w');
157 my $hb = IO
::Handle-
>new_from_fd($wb, 'w');
159 open my $diffproc, '-|', 'diff', '-up', "/dev/fd/$ra", "/dev/fd/$rb"
160 or die "failed to run program 'diff': $!";
164 open my $f1, '<', \
$a;
165 open my $f2, '<', \
$b;
168 $ha->print($line1) if defined($line1 = <$f1>);
169 $hb->print($line2) if defined($line2 = <$f2>);
170 } while (defined($line1 // $line2));
177 my $diff = <$diffproc>;
179 die "files differ:\n$diff";
182 # Write the current interface config and compare the result to a string.
186 diff
($expected, $got);
190 ## Main test execution:
192 # (sorted, it's not used right now but tests could pass on temporary files by
193 # prefixing the name with shared/ and thus you might want to split a larger
194 # test into t.01.first-part.pl, t.02.second-part.pl, etc.
197 for our $Test (sort <t
.*.pl
>) {
204 print "FAIL: $Test\n$@\n\n";
207 print "PASS: $Test\n";
211 die "$failed out of $total tests failed\n" if $failed;