]>
Commit | Line | Data |
---|---|---|
7af97ad5 | 1 | package PVE::LXC::Setup::Base; |
1c7f4f65 DM |
2 | |
3 | use strict; | |
4 | use warnings; | |
5 | ||
168d6b07 DM |
6 | use File::stat; |
7 | use Digest::SHA; | |
8 | use IO::File; | |
9 | use Encode; | |
f08b2779 | 10 | use Fcntl; |
2063d380 | 11 | use File::Path; |
f08b2779 | 12 | use File::Spec; |
168d6b07 | 13 | |
b9cd9975 | 14 | use PVE::INotify; |
55fa4e09 | 15 | use PVE::Tools; |
4401b7d4 | 16 | use PVE::Network; |
55fa4e09 | 17 | |
633a7bd8 | 18 | sub new { |
5b4657d0 | 19 | my ($class, $conf, $rootdir) = @_; |
633a7bd8 | 20 | |
5b4657d0 | 21 | return bless { conf => $conf, rootdir => $rootdir }, $class; |
633a7bd8 | 22 | } |
b9cd9975 | 23 | |
c0eae401 | 24 | sub lookup_dns_conf { |
23d928a1 | 25 | my ($self, $conf) = @_; |
b9cd9975 | 26 | |
27916659 DM |
27 | my $nameserver = $conf->{nameserver}; |
28 | my $searchdomains = $conf->{searchdomain}; | |
b9cd9975 DM |
29 | |
30 | if (!($nameserver && $searchdomains)) { | |
31 | ||
27916659 | 32 | if ($conf->{'testmode'}) { |
b9cd9975 DM |
33 | |
34 | $nameserver = "8.8.8.8 8.8.8.9"; | |
e03c2cc7 | 35 | $searchdomains = "proxmox.com"; |
b9cd9975 DM |
36 | |
37 | } else { | |
38 | ||
23d928a1 | 39 | my $host_resolv_conf = $self->{host_resolv_conf}; |
b9cd9975 DM |
40 | |
41 | $searchdomains = $host_resolv_conf->{search}; | |
42 | ||
43 | my @list = (); | |
44 | foreach my $k ("dns1", "dns2", "dns3") { | |
45 | if (my $ns = $host_resolv_conf->{$k}) { | |
46 | push @list, $ns; | |
47 | } | |
48 | } | |
49 | $nameserver = join(' ', @list); | |
50 | } | |
51 | } | |
52 | ||
53 | return ($searchdomains, $nameserver); | |
c0eae401 | 54 | } |
b9cd9975 | 55 | |
c0eae401 | 56 | sub update_etc_hosts { |
e4929e97 | 57 | my ($etc_hosts_data, $hostip, $oldname, $newname, $searchdomains) = @_; |
1c7f4f65 | 58 | |
1c7f4f65 DM |
59 | my $done = 0; |
60 | ||
61 | my @lines; | |
e4929e97 | 62 | |
ce289e3c WB |
63 | my $namepart = ($newname =~ s/\..*$//r); |
64 | ||
005f91ad | 65 | my $all_names = ''; |
ce289e3c | 66 | if ($newname =~ /\./) { |
005f91ad | 67 | $all_names .= "$newname $namepart"; |
ce289e3c WB |
68 | } else { |
69 | foreach my $domain (PVE::Tools::split_list($searchdomains)) { | |
005f91ad WB |
70 | $all_names .= ' ' if $all_names; |
71 | $all_names .= "$newname.$domain"; | |
ce289e3c | 72 | } |
005f91ad WB |
73 | $all_names .= ' ' if $all_names; |
74 | $all_names .= $newname; | |
e4929e97 | 75 | } |
1c7f4f65 DM |
76 | |
77 | foreach my $line (split(/\n/, $etc_hosts_data)) { | |
78 | if ($line =~ m/^#/ || $line =~ m/^\s*$/) { | |
79 | push @lines, $line; | |
80 | next; | |
81 | } | |
82 | ||
83 | my ($ip, @names) = split(/\s+/, $line); | |
84 | if (($ip eq '127.0.0.1') || ($ip eq '::1')) { | |
85 | push @lines, $line; | |
86 | next; | |
87 | } | |
c325b32f | 88 | |
1c7f4f65 DM |
89 | my $found = 0; |
90 | foreach my $name (@names) { | |
91 | if ($name eq $oldname || $name eq $newname) { | |
92 | $found = 1; | |
93 | } else { | |
94 | # fixme: record extra names? | |
95 | } | |
96 | } | |
97 | $found = 1 if defined($hostip) && ($ip eq $hostip); | |
98 | ||
99 | if ($found) { | |
100 | if (!$done) { | |
101 | if (defined($hostip)) { | |
005f91ad | 102 | push @lines, "$hostip $all_names"; |
1c7f4f65 | 103 | } else { |
ce289e3c | 104 | push @lines, "127.0.1.1 $namepart"; |
1c7f4f65 DM |
105 | } |
106 | $done = 1; | |
107 | } | |
108 | next; | |
109 | } else { | |
110 | push @lines, $line; | |
111 | } | |
112 | } | |
113 | ||
114 | if (!$done) { | |
115 | if (defined($hostip)) { | |
005f91ad | 116 | push @lines, "$hostip $all_names"; |
1c7f4f65 | 117 | } else { |
ce289e3c | 118 | push @lines, "127.0.1.1 $namepart"; |
1c7f4f65 DM |
119 | } |
120 | } | |
121 | ||
1e180f97 DM |
122 | my $found_localhost = 0; |
123 | foreach my $line (@lines) { | |
124 | if ($line =~ m/^127.0.0.1\s/) { | |
125 | $found_localhost = 1; | |
126 | last; | |
127 | } | |
128 | } | |
129 | ||
130 | if (!$found_localhost) { | |
131 | unshift @lines, "127.0.0.1 localhost.localnet localhost"; | |
132 | } | |
133 | ||
1c7f4f65 DM |
134 | $etc_hosts_data = join("\n", @lines) . "\n"; |
135 | ||
136 | return $etc_hosts_data; | |
c0eae401 | 137 | } |
1c7f4f65 | 138 | |
142444d5 DM |
139 | sub template_fixup { |
140 | my ($self, $conf) = @_; | |
141 | ||
142 | # do nothing by default | |
143 | } | |
144 | ||
c325b32f | 145 | sub set_dns { |
633a7bd8 | 146 | my ($self, $conf) = @_; |
c325b32f | 147 | |
23d928a1 | 148 | my ($searchdomains, $nameserver) = $self->lookup_dns_conf($conf); |
c325b32f | 149 | |
c325b32f DM |
150 | my $data = ''; |
151 | ||
152 | $data .= "search " . join(' ', PVE::Tools::split_list($searchdomains)) . "\n" | |
153 | if $searchdomains; | |
154 | ||
155 | foreach my $ns ( PVE::Tools::split_list($nameserver)) { | |
156 | $data .= "nameserver $ns\n"; | |
157 | } | |
158 | ||
f08b2779 | 159 | $self->ct_file_set_contents("/etc/resolv.conf", $data); |
c325b32f DM |
160 | } |
161 | ||
1c7f4f65 | 162 | sub set_hostname { |
633a7bd8 | 163 | my ($self, $conf) = @_; |
1c7f4f65 | 164 | |
27916659 | 165 | my $hostname = $conf->{hostname} || 'localhost'; |
1c7f4f65 | 166 | |
ce289e3c | 167 | my $namepart = ($hostname =~ s/\..*$//r); |
1c7f4f65 | 168 | |
f08b2779 | 169 | my $hostname_fn = "/etc/hostname"; |
1c7f4f65 | 170 | |
f08b2779 | 171 | my $oldname = $self->ct_file_read_firstline($hostname_fn) || 'localhost'; |
1c7f4f65 | 172 | |
f08b2779 | 173 | my $hosts_fn = "/etc/hosts"; |
1c7f4f65 DM |
174 | my $etc_hosts_data = ''; |
175 | ||
f08b2779 WB |
176 | if ($self->ct_file_exists($hosts_fn)) { |
177 | $etc_hosts_data = $self->ct_file_get_contents($hosts_fn); | |
1c7f4f65 DM |
178 | } |
179 | ||
c325b32f DM |
180 | my ($ipv4, $ipv6) = PVE::LXC::get_primary_ips($conf); |
181 | my $hostip = $ipv4 || $ipv6; | |
b9cd9975 | 182 | |
23d928a1 | 183 | my ($searchdomains) = $self->lookup_dns_conf($conf); |
b9cd9975 | 184 | |
c0eae401 DM |
185 | $etc_hosts_data = update_etc_hosts($etc_hosts_data, $hostip, $oldname, |
186 | $hostname, $searchdomains); | |
b9cd9975 | 187 | |
ce289e3c | 188 | $self->ct_file_set_contents($hostname_fn, "$namepart\n"); |
f08b2779 | 189 | $self->ct_file_set_contents($hosts_fn, $etc_hosts_data); |
1c7f4f65 DM |
190 | } |
191 | ||
55fa4e09 | 192 | sub setup_network { |
633a7bd8 | 193 | my ($self, $conf) = @_; |
55fa4e09 DM |
194 | |
195 | die "please implement this inside subclass" | |
196 | } | |
197 | ||
d66768a2 | 198 | sub setup_init { |
633a7bd8 | 199 | my ($self, $conf) = @_; |
1c7f4f65 | 200 | |
d66768a2 DM |
201 | die "please implement this inside subclass" |
202 | } | |
203 | ||
9143dec4 DM |
204 | sub setup_systemd_console { |
205 | my ($self, $conf) = @_; | |
206 | ||
f08b2779 | 207 | my $systemd_dir_rel = -x "/lib/systemd/systemd" ? |
9143dec4 DM |
208 | "/lib/systemd/system" : "/usr/lib/systemd/system"; |
209 | ||
9143dec4 DM |
210 | my $systemd_getty_service_rel = "$systemd_dir_rel/getty\@.service"; |
211 | ||
f08b2779 | 212 | return if !$self->ct_file_exists($systemd_getty_service_rel); |
9143dec4 | 213 | |
f08b2779 | 214 | my $raw = $self->ct_file_get_contents($systemd_getty_service_rel); |
9143dec4 | 215 | |
c69ae0d0 | 216 | my $systemd_container_getty_service_rel = "$systemd_dir_rel/container-getty\@.service"; |
c69ae0d0 DM |
217 | |
218 | # systemd on CenoOS 7.1 is too old (version 205), so there is no | |
219 | # container-getty service | |
f08b2779 | 220 | if (!$self->ct_file_exists($systemd_container_getty_service_rel)) { |
c69ae0d0 | 221 | if ($raw =~ s!^ConditionPathExists=/dev/tty0$!ConditionPathExists=/dev/tty!m) { |
f08b2779 | 222 | $self->ct_file_set_contents($systemd_getty_service_rel, $raw); |
c69ae0d0 DM |
223 | } |
224 | } else { | |
225 | # undo above change (in case someone updated systemd) | |
226 | if ($raw =~ s!^ConditionPathExists=/dev/tty$!ConditionPathExists=/dev/tty0!m) { | |
f08b2779 | 227 | $self->ct_file_set_contents($systemd_getty_service_rel, $raw); |
c69ae0d0 | 228 | } |
9143dec4 DM |
229 | } |
230 | ||
0d0ca400 | 231 | my $ttycount = PVE::LXC::get_tty_count($conf); |
9143dec4 DM |
232 | |
233 | for (my $i = 1; $i < 7; $i++) { | |
f08b2779 | 234 | my $tty_service_lnk = "/etc/systemd/system/getty.target.wants/getty\@tty$i.service"; |
9143dec4 | 235 | if ($i > $ttycount) { |
f08b2779 | 236 | $self->ct_unlink($tty_service_lnk); |
9143dec4 | 237 | } else { |
f08b2779 WB |
238 | if (!$self->ct_is_symlink($tty_service_lnk)) { |
239 | $self->ct_unlink($tty_service_lnk); | |
240 | $self->ct_symlink($systemd_getty_service_rel, $tty_service_lnk); | |
9143dec4 DM |
241 | } |
242 | } | |
243 | } | |
244 | } | |
245 | ||
c1d32b55 WB |
246 | sub setup_systemd_networkd { |
247 | my ($self, $conf) = @_; | |
248 | ||
c1d32b55 WB |
249 | foreach my $k (keys %$conf) { |
250 | next if $k !~ m/^net(\d+)$/; | |
251 | my $d = PVE::LXC::parse_lxc_network($conf->{$k}); | |
252 | next if !$d->{name}; | |
253 | ||
f08b2779 | 254 | my $filename = "/etc/systemd/network/$d->{name}.network"; |
c1d32b55 WB |
255 | |
256 | my $data = <<"DATA"; | |
257 | [Match] | |
258 | Name = $d->{name} | |
259 | ||
260 | [Network] | |
261 | Description = Interface $d->{name} autoconfigured by PVE | |
262 | DATA | |
4401b7d4 WB |
263 | |
264 | my $routes = ''; | |
265 | my ($has_ipv4, $has_ipv6); | |
266 | ||
c1d32b55 WB |
267 | # DHCP bitflags: |
268 | my @DHCPMODES = ('none', 'v4', 'v6', 'both'); | |
269 | my ($NONE, $DHCP4, $DHCP6, $BOTH) = (0, 1, 2, 3); | |
270 | my $dhcp = $NONE; | |
271 | ||
272 | if (defined(my $ip = $d->{ip})) { | |
273 | if ($ip eq 'dhcp') { | |
274 | $dhcp |= $DHCP4; | |
275 | } elsif ($ip ne 'manual') { | |
4401b7d4 | 276 | $has_ipv4 = 1; |
c1d32b55 WB |
277 | $data .= "Address = $ip\n"; |
278 | } | |
279 | } | |
280 | if (defined(my $gw = $d->{gw})) { | |
281 | $data .= "Gateway = $gw\n"; | |
4401b7d4 WB |
282 | if ($has_ipv4 && !PVE::Network::is_ip_in_cidr($gw, $d->{ip}, 4)) { |
283 | $routes .= "\n[Route]\nDestination = $gw/32\nScope = link\n"; | |
284 | } | |
c1d32b55 WB |
285 | } |
286 | ||
287 | if (defined(my $ip = $d->{ip6})) { | |
288 | if ($ip eq 'dhcp') { | |
289 | $dhcp |= $DHCP6; | |
290 | } elsif ($ip ne 'manual') { | |
4401b7d4 | 291 | $has_ipv6 = 1; |
c1d32b55 WB |
292 | $data .= "Address = $ip\n"; |
293 | } | |
294 | } | |
295 | if (defined(my $gw = $d->{gw6})) { | |
296 | $data .= "Gateway = $gw\n"; | |
4401b7d4 WB |
297 | if ($has_ipv6 && !PVE::Network::is_ip_in_cidr($gw, $d->{ip6}, 6)) { |
298 | $routes .= "\n[Route]\nDestination = $gw/128\nScope = link\n"; | |
299 | } | |
c1d32b55 WB |
300 | } |
301 | ||
302 | $data .= "DHCP = $DHCPMODES[$dhcp]\n"; | |
4401b7d4 | 303 | $data .= $routes if $routes; |
c1d32b55 | 304 | |
f08b2779 | 305 | $self->ct_file_set_contents($filename, $data); |
c1d32b55 | 306 | } |
b7cd927f WB |
307 | } |
308 | ||
309 | sub setup_securetty { | |
310 | my ($self, $conf, @add) = @_; | |
c1d32b55 | 311 | |
f08b2779 WB |
312 | my $filename = "/etc/securetty"; |
313 | my $data = $self->ct_file_get_contents($filename); | |
b7cd927f WB |
314 | chomp $data; $data .= "\n"; |
315 | foreach my $dev (@add) { | |
316 | if ($data !~ m!^\Q$dev\E\s*$!m) { | |
317 | $data .= "$dev\n"; | |
318 | } | |
319 | } | |
f08b2779 | 320 | $self->ct_file_set_contents($filename, $data); |
c1d32b55 WB |
321 | } |
322 | ||
168d6b07 | 323 | my $replacepw = sub { |
f08b2779 | 324 | my ($self, $file, $user, $epw, $shadow) = @_; |
168d6b07 DM |
325 | |
326 | my $tmpfile = "$file.$$"; | |
327 | ||
328 | eval { | |
f08b2779 | 329 | my $src = $self->ct_open_file_read($file) || |
168d6b07 DM |
330 | die "unable to open file '$file' - $!"; |
331 | ||
f08b2779 | 332 | my $st = $self->ct_stat($src) || |
168d6b07 DM |
333 | die "unable to stat file - $!"; |
334 | ||
f08b2779 | 335 | my $dst = $self->ct_open_file_write($tmpfile) || |
168d6b07 DM |
336 | die "unable to open file '$tmpfile' - $!"; |
337 | ||
338 | # copy owner and permissions | |
339 | chmod $st->mode, $dst; | |
340 | chown $st->uid, $st->gid, $dst; | |
367a7c18 DM |
341 | |
342 | my $last_change = int(time()/(60*60*24)); | |
343 | ||
344 | if ($epw =~ m/^\$TEST\$/) { # for regression tests | |
345 | $last_change = 12345; | |
346 | } | |
168d6b07 DM |
347 | |
348 | while (defined (my $line = <$src>)) { | |
367a7c18 DM |
349 | if ($shadow) { |
350 | $line =~ s/^${user}:[^:]*:[^:]*:/${user}:${epw}:${last_change}:/; | |
351 | } else { | |
352 | $line =~ s/^${user}:[^:]*:/${user}:${epw}:/; | |
353 | } | |
168d6b07 DM |
354 | print $dst $line; |
355 | } | |
356 | ||
357 | $src->close() || die "close '$file' failed - $!\n"; | |
358 | $dst->close() || die "close '$tmpfile' failed - $!\n"; | |
359 | }; | |
360 | if (my $err = $@) { | |
f08b2779 | 361 | $self->ct_unlink($tmpfile); |
168d6b07 | 362 | } else { |
f08b2779 WB |
363 | $self->ct_rename($tmpfile, $file); |
364 | $self->ct_unlink($tmpfile); # in case rename fails | |
168d6b07 DM |
365 | } |
366 | }; | |
367 | ||
368 | sub set_user_password { | |
633a7bd8 | 369 | my ($self, $conf, $user, $opt_password) = @_; |
168d6b07 | 370 | |
f08b2779 | 371 | my $pwfile = "/etc/passwd"; |
168d6b07 | 372 | |
f08b2779 | 373 | return if !$self->ct_file_exists($pwfile); |
168d6b07 | 374 | |
f08b2779 | 375 | my $shadow = "/etc/shadow"; |
168d6b07 DM |
376 | |
377 | if (defined($opt_password)) { | |
378 | if ($opt_password !~ m/^\$/) { | |
379 | my $time = substr (Digest::SHA::sha1_base64 (time), 0, 8); | |
380 | $opt_password = crypt(encode("utf8", $opt_password), "\$1\$$time\$"); | |
381 | }; | |
382 | } else { | |
383 | $opt_password = '*'; | |
384 | } | |
385 | ||
f08b2779 WB |
386 | if ($self->ct_file_exists($shadow)) { |
387 | &$replacepw ($self, $shadow, $user, $opt_password, 1); | |
388 | &$replacepw ($self, $pwfile, $user, 'x'); | |
168d6b07 | 389 | } else { |
f08b2779 | 390 | &$replacepw ($self, $pwfile, $user, $opt_password); |
168d6b07 DM |
391 | } |
392 | } | |
393 | ||
4727bd09 DM |
394 | my $randomize_crontab = sub { |
395 | my ($self, $conf) = @_; | |
396 | ||
b5e62cd0 DM |
397 | my @files; |
398 | # Note: dir_glob_foreach() untaints filenames! | |
f08b2779 | 399 | PVE::Tools::dir_glob_foreach("/etc/cron.d", qr/[A-Z\-\_a-z0-9]+/, sub { |
b5e62cd0 | 400 | my ($name) = @_; |
f08b2779 | 401 | push @files, "/etc/cron.d/$name"; |
b5e62cd0 | 402 | }); |
4727bd09 | 403 | |
f08b2779 WB |
404 | my $crontab_fn = "/etc/crontab"; |
405 | unshift @files, $crontab_fn if $self->ct_file_exists($crontab_fn); | |
4727bd09 DM |
406 | |
407 | foreach my $filename (@files) { | |
f08b2779 | 408 | my $data = $self->ct_file_get_contents($filename); |
4727bd09 DM |
409 | my $new = ''; |
410 | foreach my $line (split(/\n/, $data)) { | |
411 | # we only randomize minutes for root crontab entries | |
412 | if ($line =~ m/^\d+(\s+\S+\s+\S+\s+\S+\s+\S+\s+root\s+\S.*)$/) { | |
413 | my $rest = $1; | |
414 | my $min = int(rand()*59); | |
415 | $new .= "$min$rest\n"; | |
416 | } else { | |
417 | $new .= "$line\n"; | |
418 | } | |
419 | } | |
f08b2779 | 420 | $self->ct_file_set_contents($filename, $new); |
4727bd09 DM |
421 | } |
422 | }; | |
423 | ||
d66768a2 | 424 | sub pre_start_hook { |
633a7bd8 | 425 | my ($self, $conf) = @_; |
d66768a2 | 426 | |
633a7bd8 DM |
427 | $self->setup_init($conf); |
428 | $self->setup_network($conf); | |
429 | $self->set_hostname($conf); | |
430 | $self->set_dns($conf); | |
d66768a2 DM |
431 | |
432 | # fixme: what else ? | |
433 | } | |
434 | ||
435 | sub post_create_hook { | |
633a7bd8 | 436 | my ($self, $conf, $root_password) = @_; |
d66768a2 | 437 | |
142444d5 | 438 | $self->template_fixup($conf); |
4727bd09 DM |
439 | |
440 | &$randomize_crontab($self, $conf); | |
441 | ||
633a7bd8 DM |
442 | $self->set_user_password($conf, 'root', $root_password); |
443 | $self->setup_init($conf); | |
444 | $self->setup_network($conf); | |
445 | $self->set_hostname($conf); | |
446 | $self->set_dns($conf); | |
168d6b07 | 447 | |
55fa4e09 | 448 | # fixme: what else ? |
1c7f4f65 DM |
449 | } |
450 | ||
f08b2779 WB |
451 | # File access wrappers for container setup code. |
452 | # For user-namespace support these might need to take uid and gid maps into account. | |
453 | ||
c6a605f9 WB |
454 | sub ct_reset_ownership { |
455 | my ($self, @files) = @_; | |
456 | my $conf = $self->{conf}; | |
457 | return if !$self->{id_map}; | |
458 | my $uid = $self->{rootuid}; | |
459 | my $gid = $self->{rootgid}; | |
460 | chown($uid, $gid, @files); | |
461 | } | |
462 | ||
2063d380 WB |
463 | sub ct_mkdir { |
464 | my ($self, $file, $mask) = @_; | |
f08b2779 | 465 | # mkdir goes by parameter count - an `undef' mode acts like a mode of 0000 |
c6a605f9 WB |
466 | if (defined($mask)) { |
467 | return CORE::mkdir($file, $mask) && $self->ct_reset_ownership($file); | |
468 | } else { | |
469 | return CORE::mkdir($file) && $self->ct_reset_ownership($file); | |
470 | } | |
2063d380 WB |
471 | } |
472 | ||
473 | sub ct_unlink { | |
f08b2779 WB |
474 | my ($self, @files) = @_; |
475 | foreach my $file (@files) { | |
476 | CORE::unlink($file); | |
477 | } | |
478 | } | |
479 | ||
480 | sub ct_rename { | |
481 | my ($self, $old, $new) = @_; | |
482 | CORE::rename($old, $new); | |
2063d380 WB |
483 | } |
484 | ||
f08b2779 | 485 | sub ct_open_file_read { |
2063d380 | 486 | my $self = shift; |
f08b2779 WB |
487 | my $file = shift; |
488 | return IO::File->new($file, O_RDONLY, @_); | |
2063d380 WB |
489 | } |
490 | ||
f08b2779 | 491 | sub ct_open_file_write { |
2063d380 | 492 | my $self = shift; |
f08b2779 | 493 | my $file = shift; |
c6a605f9 WB |
494 | my $fh = IO::File->new($file, O_WRONLY | O_CREAT, @_); |
495 | $self->ct_reset_ownership($fh); | |
496 | return $fh; | |
2063d380 WB |
497 | } |
498 | ||
f08b2779 | 499 | sub ct_make_path { |
2063d380 | 500 | my $self = shift; |
c6a605f9 WB |
501 | if ($self->{id_map}) { |
502 | my $opts = pop; | |
503 | if (ref($opts) eq 'HASH') { | |
504 | $opts->{owner} = $self->{rootuid} if !defined($self->{owner}); | |
505 | $opts->{group} = $self->{rootgid} if !defined($self->{group}); | |
506 | } | |
507 | File::Path::make_path(@_, $opts); | |
508 | } else { | |
509 | File::Path::make_path(@_); | |
510 | } | |
2063d380 WB |
511 | } |
512 | ||
513 | sub ct_symlink { | |
514 | my ($self, $old, $new) = @_; | |
f08b2779 | 515 | return CORE::symlink($old, $new); |
2063d380 WB |
516 | } |
517 | ||
518 | sub ct_file_exists { | |
519 | my ($self, $file) = @_; | |
f08b2779 WB |
520 | return -f $file; |
521 | } | |
522 | ||
523 | sub ct_is_directory { | |
524 | my ($self, $file) = @_; | |
525 | return -d $file; | |
526 | } | |
527 | ||
528 | sub ct_is_symlink { | |
529 | my ($self, $file) = @_; | |
530 | return -l $file; | |
531 | } | |
532 | ||
533 | sub ct_stat { | |
534 | my ($self, $file) = @_; | |
535 | return File::stat::stat($file); | |
2063d380 WB |
536 | } |
537 | ||
538 | sub ct_file_read_firstline { | |
539 | my ($self, $file) = @_; | |
f08b2779 | 540 | return PVE::Tools::file_read_firstline($file); |
2063d380 WB |
541 | } |
542 | ||
543 | sub ct_file_get_contents { | |
544 | my ($self, $file) = @_; | |
f08b2779 | 545 | return PVE::Tools::file_get_contents($file); |
2063d380 WB |
546 | } |
547 | ||
548 | sub ct_file_set_contents { | |
39243220 | 549 | my ($self, $file, $data, $perms) = @_; |
c6a605f9 WB |
550 | PVE::Tools::file_set_contents($file, $data, $perms); |
551 | $self->ct_reset_ownership($file); | |
2063d380 WB |
552 | } |
553 | ||
fa7cb12b WB |
554 | # Modify a marked portion of a file and move it to the beginning of the file. |
555 | # If the file becomes empty it will be deleted. | |
556 | sub ct_modify_file_head_portion { | |
557 | my ($self, $file, $head, $tail, $data) = @_; | |
558 | if ($self->ct_file_exists($file)) { | |
559 | my $old = $self->ct_file_get_contents($file); | |
560 | # remove the portion between $head and $tail (all instances via /g) | |
561 | $old =~ s/(?:^|(?<=\n))\Q$head\E.*\Q$tail\E//gs; | |
562 | chomp $old; | |
563 | if ($old) { | |
564 | # old data existed, append and add the trailing newline | |
565 | if ($data) { | |
566 | $self->ct_file_set_contents($file, $head.$data.$tail . $old."\n"); | |
567 | } else { | |
568 | $self->ct_file_set_contents($file, $old."\n"); | |
569 | } | |
570 | } elsif ($data) { | |
571 | # only our own data will be added | |
572 | $self->ct_file_set_contents($file, $head.$data.$tail); | |
573 | } else { | |
574 | # empty => delete | |
575 | $self->ct_unlink($file); | |
576 | } | |
577 | } else { | |
578 | $self->ct_file_set_contents($file, $head.$data.$tail); | |
579 | } | |
580 | } | |
581 | ||
1c7f4f65 | 582 | 1; |