]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
code cleanup
[pve-container.git] / src / PVE / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::LXC;
2
3use strict;
4use warnings;
d14a9a1b 5use POSIX qw(EINTR);
f76a2828
DM
6
7use File::Path;
8use Fcntl ':flock';
9
10use PVE::Cluster qw(cfs_register_file cfs_read_file);
c65e0a6d 11use PVE::Storage;
f76a2828
DM
12use PVE::SafeSyslog;
13use PVE::INotify;
a3249355 14use PVE::JSONSchema qw(get_standard_option);
6aca6740 15use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
68fba17b 16use PVE::Network;
52389a07 17use PVE::AccessControl;
f76a2828
DM
18
19use Data::Dumper;
20
27916659
DM
21my $nodename = PVE::INotify::nodename();
22
23cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
f76a2828 24
7dfc49cc
DM
25PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
26sub verify_lxc_network {
27 my ($value, $noerr) = @_;
28
29 return $value if parse_lxc_network($value);
30
31 return undef if $noerr;
32
33 die "unable to parse network setting\n";
34}
35
27916659
DM
36PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
37sub verify_ct_mountpoint {
38 my ($value, $noerr) = @_;
822de0c3 39
27916659 40 return $value if parse_ct_mountpoint($value);
822de0c3 41
27916659 42 return undef if $noerr;
822de0c3 43
27916659 44 die "unable to parse CT mountpoint options\n";
822de0c3
DM
45}
46
27916659
DM
47PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
48 type => 'string', format => 'pve-ct-mountpoint',
49 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
8fbd2935 50 description => "Use volume as container root.",
27916659
DM
51 optional => 1,
52});
53
52389a07
DM
54PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
55 description => "The name of the snapshot.",
56 type => 'string', format => 'pve-configid',
57 maxLength => 40,
58});
59
27916659 60my $confdesc = {
09d3ec42
DM
61 lock => {
62 optional => 1,
63 type => 'string',
64 description => "Lock/unlock the VM.",
65 enum => [qw(migrate backup snapshot rollback)],
66 },
27916659
DM
67 onboot => {
68 optional => 1,
69 type => 'boolean',
70 description => "Specifies whether a VM will be started during system bootup.",
71 default => 0,
117636e5 72 },
27916659 73 startup => get_standard_option('pve-startup-order'),
bb1ac2de
DM
74 template => {
75 optional => 1,
76 type => 'boolean',
77 description => "Enable/disable Template.",
78 default => 0,
79 },
27916659
DM
80 arch => {
81 optional => 1,
82 type => 'string',
83 enum => ['amd64', 'i386'],
84 description => "OS architecture type.",
85 default => 'amd64',
117636e5 86 },
27916659
DM
87 ostype => {
88 optional => 1,
89 type => 'string',
90 enum => ['debian', 'ubuntu', 'centos'],
91 description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
a3249355 92 },
4f958489
DM
93 console => {
94 optional => 1,
95 type => 'boolean',
96 description => "Attach a console device (/dev/console) to the container.",
97 default => 1,
98 },
27916659
DM
99 tty => {
100 optional => 1,
101 type => 'integer',
102 description => "Specify the number of tty available to the container",
103 minimum => 0,
104 maximum => 6,
0d0ca400 105 default => 2,
611fe3aa 106 },
27916659
DM
107 cpulimit => {
108 optional => 1,
109 type => 'number',
110 description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
111 minimum => 0,
112 maximum => 128,
113 default => 0,
114 },
115 cpuunits => {
116 optional => 1,
117 type => 'integer',
118 description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
119 minimum => 0,
120 maximum => 500000,
81bee809 121 default => 1024,
27916659
DM
122 },
123 memory => {
124 optional => 1,
125 type => 'integer',
126 description => "Amount of RAM for the VM in MB.",
127 minimum => 16,
128 default => 512,
129 },
130 swap => {
131 optional => 1,
132 type => 'integer',
133 description => "Amount of SWAP for the VM in MB.",
134 minimum => 0,
135 default => 512,
136 },
137 hostname => {
138 optional => 1,
139 description => "Set a host name for the container.",
140 type => 'string',
141 maxLength => 255,
142 },
143 description => {
144 optional => 1,
145 type => 'string',
146 description => "Container description. Only used on the configuration web interface.",
147 },
148 searchdomain => {
149 optional => 1,
150 type => 'string',
151 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
152 },
153 nameserver => {
154 optional => 1,
155 type => 'string',
156 description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
157 },
158 rootfs => get_standard_option('pve-ct-rootfs'),
09d3ec42
DM
159 parent => {
160 optional => 1,
161 type => 'string', format => 'pve-configid',
162 maxLength => 40,
163 description => "Parent snapshot name. This is used internally, and should not be modified.",
164 },
165 snaptime => {
166 optional => 1,
167 description => "Timestamp for snapshots.",
168 type => 'integer',
169 minimum => 0,
170 },
aca816ad
DM
171 cmode => {
172 optional => 1,
173 description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
174 type => 'string',
175 enum => ['shell', 'console', 'tty'],
176 default => 'tty',
177 },
f76a2828
DM
178};
179
e576f689
DM
180my $valid_lxc_conf_keys = {
181 'lxc.include' => 1,
182 'lxc.arch' => 1,
183 'lxc.utsname' => 1,
184 'lxc.haltsignal' => 1,
185 'lxc.rebootsignal' => 1,
186 'lxc.stopsignal' => 1,
187 'lxc.init_cmd' => 1,
188 'lxc.network.type' => 1,
189 'lxc.network.flags' => 1,
190 'lxc.network.link' => 1,
191 'lxc.network.mtu' => 1,
192 'lxc.network.name' => 1,
193 'lxc.network.hwaddr' => 1,
194 'lxc.network.ipv4' => 1,
195 'lxc.network.ipv4.gateway' => 1,
196 'lxc.network.ipv6' => 1,
197 'lxc.network.ipv6.gateway' => 1,
198 'lxc.network.script.up' => 1,
199 'lxc.network.script.down' => 1,
200 'lxc.pts' => 1,
201 'lxc.console.logfile' => 1,
202 'lxc.console' => 1,
203 'lxc.tty' => 1,
204 'lxc.devttydir' => 1,
205 'lxc.hook.autodev' => 1,
206 'lxc.autodev' => 1,
207 'lxc.kmsg' => 1,
208 'lxc.mount' => 1,
209 'lxc.mount.entry' => 1,
210 'lxc.mount.auto' => 1,
211 'lxc.rootfs' => 1,
212 'lxc.rootfs.mount' => 1,
213 'lxc.rootfs.options' => 1,
214 # lxc.cgroup.*
215 'lxc.cap.drop' => 1,
216 'lxc.cap.keep' => 1,
217 'lxc.aa_profile' => 1,
218 'lxc.aa_allow_incomplete' => 1,
219 'lxc.se_context' => 1,
220 'lxc.seccomp' => 1,
221 'lxc.id_map' => 1,
222 'lxc.hook.pre-start' => 1,
223 'lxc.hook.pre-mount' => 1,
224 'lxc.hook.mount' => 1,
225 'lxc.hook.start' => 1,
226 'lxc.hook.post-stop' => 1,
227 'lxc.hook.clone' => 1,
228 'lxc.hook.destroy' => 1,
229 'lxc.loglevel' => 1,
230 'lxc.logfile' => 1,
231 'lxc.start.auto' => 1,
232 'lxc.start.delay' => 1,
233 'lxc.start.order' => 1,
234 'lxc.group' => 1,
235 'lxc.environment' => 1,
236 'lxc.' => 1,
237 'lxc.' => 1,
238 'lxc.' => 1,
239 'lxc.' => 1,
240};
241
27916659
DM
242my $MAX_LXC_NETWORKS = 10;
243for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
244 $confdesc->{"net$i"} = {
245 optional => 1,
246 type => 'string', format => 'pve-lxc-network',
247 description => "Specifies network interfaces for the container.\n\n".
248 "The string should have the follow format:\n\n".
249 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
250 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
251 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
252 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
253 };
90bc31f7
DM
254}
255
02c9d10c
AD
256my $MAX_MOUNT_POINTS = 10;
257for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
258 $confdesc->{"mp$i"} = {
259 optional => 1,
260 type => 'string', format => 'pve-ct-mountpoint',
261 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
566d5f81 262 description => "Use volume as container mount point (experimental feature).",
02c9d10c
AD
263 optional => 1,
264 };
265}
266
27916659
DM
267sub write_pct_config {
268 my ($filename, $conf) = @_;
f76a2828 269
27916659 270 delete $conf->{snapstate}; # just to be sure
f76a2828 271
27916659
DM
272 my $generate_raw_config = sub {
273 my ($conf) = @_;
f76a2828 274
27916659 275 my $raw = '';
cbb03fea 276
27916659
DM
277 # add description as comment to top of file
278 my $descr = $conf->{description} || '';
279 foreach my $cl (split(/\n/, $descr)) {
280 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
a12a36e0 281 }
fff3a342 282
27916659 283 foreach my $key (sort keys %$conf) {
09d3ec42 284 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
e576f689 285 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
27916659 286 $raw .= "$key: $conf->{$key}\n";
a12a36e0 287 }
e576f689
DM
288
289 if (my $lxcconf = $conf->{lxc}) {
290 foreach my $entry (@$lxcconf) {
291 my ($k, $v) = @$entry;
292 $raw .= "$k: $v\n";
293 }
294 }
295
27916659 296 return $raw;
a12a36e0 297 };
160f0941 298
27916659 299 my $raw = &$generate_raw_config($conf);
a12a36e0 300
27916659
DM
301 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
302 $raw .= "\n[$snapname]\n";
303 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
f76a2828
DM
304 }
305
f76a2828
DM
306 return $raw;
307}
308
27916659
DM
309sub check_type {
310 my ($key, $value) = @_;
822de0c3 311
27916659 312 die "unknown setting '$key'\n" if !$confdesc->{$key};
822de0c3 313
27916659
DM
314 my $type = $confdesc->{$key}->{type};
315
316 if (!defined($value)) {
317 die "got undefined value\n";
318 }
319
320 if ($value =~ m/[\n\r]/) {
321 die "property contains a line feed\n";
322 }
822de0c3 323
27916659
DM
324 if ($type eq 'boolean') {
325 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
326 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
327 die "type check ('boolean') failed - got '$value'\n";
328 } elsif ($type eq 'integer') {
329 return int($1) if $value =~ m/^(\d+)$/;
330 die "type check ('integer') failed - got '$value'\n";
331 } elsif ($type eq 'number') {
332 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
333 die "type check ('number') failed - got '$value'\n";
334 } elsif ($type eq 'string') {
335 if (my $fmt = $confdesc->{$key}->{format}) {
336 PVE::JSONSchema::check_format($fmt, $value);
337 return $value;
338 }
cbb03fea 339 return $value;
822de0c3 340 } else {
27916659 341 die "internal error"
822de0c3 342 }
822de0c3
DM
343}
344
27916659 345sub parse_pct_config {
f76a2828
DM
346 my ($filename, $raw) = @_;
347
348 return undef if !defined($raw);
349
27916659 350 my $res = {
f76a2828 351 digest => Digest::SHA::sha1_hex($raw),
27916659 352 snapshots => {},
f76a2828
DM
353 };
354
27916659 355 $filename =~ m|/lxc/(\d+).conf$|
f76a2828
DM
356 || die "got strange filename '$filename'";
357
358 my $vmid = $1;
359
27916659
DM
360 my $conf = $res;
361 my $descr = '';
362 my $section = '';
363
364 my @lines = split(/\n/, $raw);
365 foreach my $line (@lines) {
366 next if $line =~ m/^\s*$/;
367
368 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
369 $section = $1;
370 $conf->{description} = $descr if $descr;
371 $descr = '';
372 $conf = $res->{snapshots}->{$section} = {};
373 next;
a12a36e0 374 }
a12a36e0 375
27916659
DM
376 if ($line =~ m/^\#(.*)\s*$/) {
377 $descr .= PVE::Tools::decode_text($1) . "\n";
378 next;
f76a2828 379 }
5d186e16 380
b43a097e 381 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
e576f689
DM
382 my $key = $1;
383 my $value = $3;
384 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
385 push @{$conf->{lxc}}, [$key, $value];
386 } else {
387 warn "vm $vmid - unable to parse config: $line\n";
388 }
389 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
27916659
DM
390 $descr .= PVE::Tools::decode_text($2);
391 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
392 $conf->{snapstate} = $1;
393 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
394 my $key = $1;
5d186e16 395 my $value = $2;
27916659
DM
396 eval { $value = check_type($key, $value); };
397 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
398 $conf->{$key} = $value;
5d186e16 399 } else {
27916659 400 warn "vm $vmid - unable to parse config: $line\n";
5d186e16 401 }
7dfc49cc
DM
402 }
403
27916659 404 $conf->{description} = $descr if $descr;
5d186e16 405
27916659
DM
406 delete $res->{snapstate}; # just to be sure
407
408 return $res;
f76a2828
DM
409}
410
411sub config_list {
412 my $vmlist = PVE::Cluster::get_vmlist();
413 my $res = {};
414 return $res if !$vmlist || !$vmlist->{ids};
415 my $ids = $vmlist->{ids};
416
417 foreach my $vmid (keys %$ids) {
418 next if !$vmid; # skip CT0
419 my $d = $ids->{$vmid};
420 next if !$d->{node} || $d->{node} ne $nodename;
421 next if !$d->{type} || $d->{type} ne 'lxc';
422 $res->{$vmid}->{type} = 'lxc';
423 }
424 return $res;
425}
426
427sub cfs_config_path {
428 my ($vmid, $node) = @_;
429
430 $node = $nodename if !$node;
27916659 431 return "nodes/$node/lxc/$vmid.conf";
f76a2828
DM
432}
433
9c2d4ce9
DM
434sub config_file {
435 my ($vmid, $node) = @_;
436
437 my $cfspath = cfs_config_path($vmid, $node);
438 return "/etc/pve/$cfspath";
439}
440
f76a2828
DM
441sub load_config {
442 my ($vmid) = @_;
443
444 my $cfspath = cfs_config_path($vmid);
445
446 my $conf = PVE::Cluster::cfs_read_file($cfspath);
447 die "container $vmid does not exists\n" if !defined($conf);
448
449 return $conf;
450}
451
5b4657d0
DM
452sub create_config {
453 my ($vmid, $conf) = @_;
454
455 my $dir = "/etc/pve/nodes/$nodename/lxc";
456 mkdir $dir;
457
5b4657d0
DM
458 write_config($vmid, $conf);
459}
460
461sub destroy_config {
462 my ($vmid) = @_;
463
27916659 464 unlink config_file($vmid, $nodename);
5b4657d0
DM
465}
466
f76a2828
DM
467sub write_config {
468 my ($vmid, $conf) = @_;
469
470 my $cfspath = cfs_config_path($vmid);
471
472 PVE::Cluster::cfs_write_file($cfspath, $conf);
473}
474
d14a9a1b
DM
475# flock: we use one file handle per process, so lock file
476# can be called multiple times and succeeds for the same process.
477
478my $lock_handles = {};
479my $lockdir = "/run/lock/lxc";
480
481sub lock_filename {
482 my ($vmid) = @_;
cbb03fea 483
d14a9a1b
DM
484 return "$lockdir/pve-config-{$vmid}.lock";
485}
486
487sub lock_aquire {
488 my ($vmid, $timeout) = @_;
489
490 $timeout = 10 if !$timeout;
491 my $mode = LOCK_EX;
492
493 my $filename = lock_filename($vmid);
494
f99e8278
AD
495 mkdir $lockdir if !-d $lockdir;
496
d14a9a1b
DM
497 my $lock_func = sub {
498 if (!$lock_handles->{$$}->{$filename}) {
499 my $fh = new IO::File(">>$filename") ||
500 die "can't open file - $!\n";
501 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
502 }
503
504 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
505 print STDERR "trying to aquire lock...";
506 my $success;
507 while(1) {
508 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
509 # try again on EINTR (see bug #273)
510 if ($success || ($! != EINTR)) {
511 last;
512 }
513 }
514 if (!$success) {
515 print STDERR " failed\n";
516 die "can't aquire lock - $!\n";
517 }
518
519 $lock_handles->{$$}->{$filename}->{refcount}++;
cbb03fea 520
d14a9a1b
DM
521 print STDERR " OK\n";
522 }
523 };
524
525 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
526 my $err = $@;
527 if ($err) {
528 die "can't lock file '$filename' - $err";
cbb03fea 529 }
d14a9a1b
DM
530}
531
532sub lock_release {
533 my ($vmid) = @_;
534
535 my $filename = lock_filename($vmid);
536
537 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
538 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
539 if ($refcount <= 0) {
540 $lock_handles->{$$}->{$filename} = undef;
541 close ($fh);
542 }
543 }
544}
545
f76a2828
DM
546sub lock_container {
547 my ($vmid, $timeout, $code, @param) = @_;
548
d14a9a1b 549 my $res;
f76a2828 550
d14a9a1b
DM
551 lock_aquire($vmid, $timeout);
552 eval { $res = &$code(@param) };
553 my $err = $@;
554 lock_release($vmid);
f76a2828 555
d14a9a1b 556 die $err if $err;
f76a2828
DM
557
558 return $res;
559}
560
ec52ac21
DM
561sub option_exists {
562 my ($name) = @_;
563
564 return defined($confdesc->{$name});
565}
f76a2828
DM
566
567# add JSON properties for create and set function
568sub json_config_properties {
569 my $prop = shift;
570
571 foreach my $opt (keys %$confdesc) {
09d3ec42 572 next if $opt eq 'parent' || $opt eq 'snaptime';
27916659
DM
573 next if $prop->{$opt};
574 $prop->{$opt} = $confdesc->{$opt};
575 }
576
577 return $prop;
578}
579
580sub json_config_properties_no_rootfs {
581 my $prop = shift;
582
583 foreach my $opt (keys %$confdesc) {
584 next if $prop->{$opt};
09d3ec42 585 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
f76a2828
DM
586 $prop->{$opt} = $confdesc->{$opt};
587 }
588
589 return $prop;
590}
591
822de0c3
DM
592# container status helpers
593
594sub list_active_containers {
cbb03fea 595
822de0c3
DM
596 my $filename = "/proc/net/unix";
597
598 # similar test is used by lcxcontainers.c: list_active_containers
599 my $res = {};
cbb03fea 600
822de0c3
DM
601 my $fh = IO::File->new ($filename, "r");
602 return $res if !$fh;
603
604 while (defined(my $line = <$fh>)) {
605 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
606 my $path = $1;
27916659 607 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
822de0c3
DM
608 $res->{$1} = 1;
609 }
610 }
611 }
612
613 close($fh);
cbb03fea 614
822de0c3
DM
615 return $res;
616}
f76a2828 617
5c752bbf
DM
618# warning: this is slow
619sub check_running {
620 my ($vmid) = @_;
621
622 my $active_hash = list_active_containers();
623
624 return 1 if defined($active_hash->{$vmid});
cbb03fea 625
5c752bbf
DM
626 return undef;
627}
628
10fc3ba5
DM
629sub get_container_disk_usage {
630 my ($vmid) = @_;
631
632 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
cbb03fea 633
10fc3ba5
DM
634 my $res = {
635 total => 0,
636 used => 0,
637 avail => 0,
638 };
639
640 my $parser = sub {
641 my $line = shift;
642 if (my ($fsid, $total, $used, $avail) = $line =~
643 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
644 $res = {
645 total => $total,
646 used => $used,
647 avail => $avail,
648 };
649 }
650 };
651 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
652 warn $@ if $@;
653
654 return $res;
655}
656
f76a2828
DM
657sub vmstatus {
658 my ($opt_vmid) = @_;
659
660 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
661
822de0c3 662 my $active_hash = list_active_containers();
cbb03fea 663
f76a2828 664 foreach my $vmid (keys %$list) {
f76a2828 665 my $d = $list->{$vmid};
10fc3ba5
DM
666
667 my $running = defined($active_hash->{$vmid});
cbb03fea 668
10fc3ba5 669 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
670
671 my $cfspath = cfs_config_path($vmid);
238a56cb 672 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
cbb03fea 673
27916659 674 $d->{name} = $conf->{'hostname'} || "CT$vmid";
238a56cb 675 $d->{name} =~ s/[\s]//g;
cbb03fea 676
27916659 677 $d->{cpus} = $conf->{cpulimit} // 0;
44da0641 678
27916659
DM
679 if ($running) {
680 my $res = get_container_disk_usage($vmid);
681 $d->{disk} = $res->{used};
682 $d->{maxdisk} = $res->{total};
683 } else {
684 $d->{disk} = 0;
685 # use 4GB by default ??
686 if (my $rootfs = $conf->{rootfs}) {
687 my $rootinfo = parse_ct_mountpoint($rootfs);
688 $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
689 } else {
690 $d->{maxdisk} = 4*1024*1024*1024;
10fc3ba5 691 }
238a56cb 692 }
cbb03fea 693
238a56cb
DM
694 $d->{mem} = 0;
695 $d->{swap} = 0;
95df9a12
DM
696 $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
697 $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
e901d418 698
238a56cb
DM
699 $d->{uptime} = 0;
700 $d->{cpu} = 0;
e901d418 701
238a56cb
DM
702 $d->{netout} = 0;
703 $d->{netin} = 0;
f76a2828 704
238a56cb
DM
705 $d->{diskread} = 0;
706 $d->{diskwrite} = 0;
bb1ac2de
DM
707
708 $d->{template} = is_template($conf);
f76a2828 709 }
cbb03fea 710
238a56cb
DM
711 foreach my $vmid (keys %$list) {
712 my $d = $list->{$vmid};
713 next if $d->{status} ne 'running';
f76a2828 714
22a77285
DM
715 $d->{uptime} = 100; # fixme:
716
238a56cb
DM
717 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
718 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
b5289322
AD
719
720 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
1e647c7c 721 my @bytes = split(/\n/, $blkio_bytes);
b5289322 722 foreach my $byte (@bytes) {
1e647c7c
DM
723 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
724 $d->{diskread} = $2 if $key eq 'Read';
725 $d->{diskwrite} = $2 if $key eq 'Write';
726 }
b5289322 727 }
238a56cb 728 }
cbb03fea 729
f76a2828
DM
730 return $list;
731}
732
27916659
DM
733my $parse_size = sub {
734 my ($value) = @_;
735
736 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
737 my ($size, $unit) = ($1, $3);
738 if ($unit) {
739 if ($unit eq 'K') {
740 $size = $size * 1024;
741 } elsif ($unit eq 'M') {
742 $size = $size * 1024 * 1024;
743 } elsif ($unit eq 'G') {
744 $size = $size * 1024 * 1024 * 1024;
745 }
746 }
747 return int($size);
748};
749
750sub parse_ct_mountpoint {
751 my ($data) = @_;
752
753 $data //= '';
754
755 my $res = {};
756
757 foreach my $p (split (/,/, $data)) {
758 next if $p =~ m/^\s*$/;
759
02c9d10c 760 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
27916659
DM
761 my ($k, $v) = ($1, $2);
762 return undef if defined($res->{$k});
dada5f33 763 $res->{$k} = $v;
27916659
DM
764 } else {
765 if (!$res->{volume} && $p !~ m/=/) {
766 $res->{volume} = $p;
767 } else {
768 return undef;
769 }
770 }
771 }
772
773 return undef if !$res->{volume};
774
775 return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
776
777 if ($res->{size}) {
778 return undef if !defined($res->{size} = &$parse_size($res->{size}));
779 }
780
781 return $res;
782}
7dfc49cc 783
dde7b02b 784sub print_ct_mountpoint {
bb1ac2de
DM
785 my ($info) = @_;
786
787 my $opts = '';
788
789 die "missing volume\n" if !$info->{volume};
790
791 foreach my $o ('size', 'backup') {
7092c9f1 792 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
bb1ac2de
DM
793 }
794
795 return "$info->{volume}$opts";
796}
797
7dfc49cc 798sub print_lxc_network {
f76a2828
DM
799 my $net = shift;
800
bedeaaf1 801 die "no network name defined\n" if !$net->{name};
f76a2828 802
bedeaaf1 803 my $res = "name=$net->{name}";
7dfc49cc 804
bedeaaf1 805 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
806 next if !defined($net->{$k});
807 $res .= ",$k=$net->{$k}";
808 }
7dfc49cc 809
f76a2828
DM
810 return $res;
811}
812
7dfc49cc
DM
813sub parse_lxc_network {
814 my ($data) = @_;
815
816 my $res = {};
817
818 return $res if !$data;
819
820 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 821 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
822 $res->{$1} = $2;
823 } else {
824 return undef;
825 }
826 }
827
828 $res->{type} = 'veth';
93cdbbfb 829 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 830
7dfc49cc
DM
831 return $res;
832}
f76a2828 833
238a56cb
DM
834sub read_cgroup_value {
835 my ($group, $vmid, $name, $full) = @_;
836
837 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
838
839 return PVE::Tools::file_get_contents($path) if $full;
840
841 return PVE::Tools::file_read_firstline($path);
842}
843
bf0b8c43
AD
844sub write_cgroup_value {
845 my ($group, $vmid, $name, $value) = @_;
846
847 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
848 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
849
850}
851
52f1d76b
DM
852sub find_lxc_console_pids {
853
854 my $res = {};
855
856 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
857 my ($pid) = @_;
858
859 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
860 return if !$cmdline;
861
862 my @args = split(/\0/, $cmdline);
863
864 # serach for lxc-console -n <vmid>
cbb03fea 865 return if scalar(@args) != 3;
52f1d76b
DM
866 return if $args[1] ne '-n';
867 return if $args[2] !~ m/^\d+$/;
868 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 869
52f1d76b 870 my $vmid = $args[2];
cbb03fea 871
52f1d76b
DM
872 push @{$res->{$vmid}}, $pid;
873 });
874
875 return $res;
876}
877
bedeaaf1
AD
878sub find_lxc_pid {
879 my ($vmid) = @_;
880
881 my $pid = undef;
882 my $parser = sub {
883 my $line = shift;
8b25977f 884 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1
AD
885 };
886 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
887
8b25977f 888 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 889
8b25977f 890 return $pid;
bedeaaf1
AD
891}
892
55fa4e09
DM
893my $ipv4_reverse_mask = [
894 '0.0.0.0',
895 '128.0.0.0',
896 '192.0.0.0',
897 '224.0.0.0',
898 '240.0.0.0',
899 '248.0.0.0',
900 '252.0.0.0',
901 '254.0.0.0',
902 '255.0.0.0',
903 '255.128.0.0',
904 '255.192.0.0',
905 '255.224.0.0',
906 '255.240.0.0',
907 '255.248.0.0',
908 '255.252.0.0',
909 '255.254.0.0',
910 '255.255.0.0',
911 '255.255.128.0',
912 '255.255.192.0',
913 '255.255.224.0',
914 '255.255.240.0',
915 '255.255.248.0',
916 '255.255.252.0',
917 '255.255.254.0',
918 '255.255.255.0',
919 '255.255.255.128',
920 '255.255.255.192',
921 '255.255.255.224',
922 '255.255.255.240',
923 '255.255.255.248',
924 '255.255.255.252',
925 '255.255.255.254',
926 '255.255.255.255',
927];
cbb03fea
DM
928
929# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
930# CIDR networks
931sub parse_ipv4_cidr {
932 my ($cidr, $noerr) = @_;
933
934 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
935 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
936 }
cbb03fea 937
55fa4e09 938 return undef if $noerr;
cbb03fea 939
55fa4e09
DM
940 die "unable to parse ipv4 address/mask\n";
941}
93285df8 942
a12a36e0
WL
943sub check_lock {
944 my ($conf) = @_;
945
27916659 946 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
a12a36e0
WL
947}
948
27916659 949sub update_lxc_config {
c628ffa1 950 my ($storage_cfg, $vmid, $conf) = @_;
b80dd50a 951
bb1ac2de
DM
952 my $dir = "/var/lib/lxc/$vmid";
953
954 if ($conf->{template}) {
955
956 unlink "$dir/config";
957
958 return;
959 }
960
27916659 961 my $raw = '';
b80dd50a 962
27916659
DM
963 die "missing 'arch' - internal error" if !$conf->{arch};
964 $raw .= "lxc.arch = $conf->{arch}\n";
b80dd50a 965
27916659 966 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
c1d32b55 967 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
27916659
DM
968 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
969 } else {
970 die "implement me";
971 }
b80dd50a 972
6f035afe 973 if (!has_dev_console($conf)) {
eeaea429
DM
974 $raw .= "lxc.console = none\n";
975 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
976 }
4f958489 977
0d0ca400 978 my $ttycount = get_tty_count($conf);
27916659 979 $raw .= "lxc.tty = $ttycount\n";
cbb03fea 980
27916659
DM
981 my $utsname = $conf->{hostname} || "CT$vmid";
982 $raw .= "lxc.utsname = $utsname\n";
cbb03fea 983
27916659
DM
984 my $memory = $conf->{memory} || 512;
985 my $swap = $conf->{swap} // 0;
986
987 my $lxcmem = int($memory*1024*1024);
988 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
a12a36e0 989
27916659
DM
990 my $lxcswap = int(($memory + $swap)*1024*1024);
991 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
992
993 if (my $cpulimit = $conf->{cpulimit}) {
994 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
995 my $value = int(100000*$cpulimit);
996 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
a12a36e0
WL
997 }
998
27916659
DM
999 my $shares = $conf->{cpuunits} || 1024;
1000 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1001
a3076d81 1002
6aca6740
AD
1003 PVE::LXC::foreach_mountpoint($conf, sub {
1004 my ($ms, $mountpoint) = @_;
c628ffa1 1005
d410f592 1006 return if $ms ne 'rootfs';
6aca6740 1007 my $volid = $mountpoint->{volume};
27163d2f 1008 return if !$volid || $volid =~ m|^/dev/.+|;
453cb062 1009
a3076d81
AD
1010 my $path = volid_path ($volid, $storage_cfg);
1011
1012 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1013 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1014 $path = "loop:".$path if $scfg->{path};
1015
d410f592 1016 $raw .= "lxc.rootfs = $path\n";
6aca6740 1017 });
27916659
DM
1018
1019 my $netcount = 0;
1020 foreach my $k (keys %$conf) {
1021 next if $k !~ m/^net(\d+)$/;
1022 my $ind = $1;
a16d94c8 1023 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1024 $netcount++;
1025 $raw .= "lxc.network.type = veth\n";
18862537 1026 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1027 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1028 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1029 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1030 }
1031
e576f689
DM
1032 if (my $lxcconf = $conf->{lxc}) {
1033 foreach my $entry (@$lxcconf) {
1034 my ($k, $v) = @$entry;
1035 $netcount++ if $k eq 'lxc.network.type';
1036 $raw .= "$k = $v\n";
1037 }
1038 }
27916659 1039
e576f689
DM
1040 $raw .= "lxc.network.type = empty\n" if !$netcount;
1041
27916659
DM
1042 File::Path::mkpath("$dir/rootfs");
1043
1044 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1045}
1046
117636e5
DM
1047# verify and cleanup nameserver list (replace \0 with ' ')
1048sub verify_nameserver_list {
1049 my ($nameserver_list) = @_;
1050
1051 my @list = ();
1052 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1053 PVE::JSONSchema::pve_verify_ip($server);
1054 push @list, $server;
1055 }
1056
1057 return join(' ', @list);
1058}
1059
1060sub verify_searchdomain_list {
1061 my ($searchdomain_list) = @_;
1062
1063 my @list = ();
1064 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1065 # todo: should we add checks for valid dns domains?
1066 push @list, $server;
1067 }
1068
1069 return join(' ', @list);
1070}
1071
27916659 1072sub update_pct_config {
93285df8
DM
1073 my ($vmid, $conf, $running, $param, $delete) = @_;
1074
bf0b8c43
AD
1075 my @nohotplug;
1076
cbb03fea
DM
1077 my $rootdir;
1078 if ($running) {
bedeaaf1 1079 my $pid = find_lxc_pid($vmid);
cbb03fea 1080 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1081 }
1082
93285df8
DM
1083 if (defined($delete)) {
1084 foreach my $opt (@$delete) {
27916659 1085 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1086 die "unable to delete required option '$opt'\n";
1087 } elsif ($opt eq 'swap') {
27916659 1088 delete $conf->{$opt};
bf0b8c43 1089 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1090 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1091 delete $conf->{$opt};
4f958489 1092 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1093 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
27916659 1094 delete $conf->{$opt};
bf0b8c43
AD
1095 push @nohotplug, $opt;
1096 next if $running;
68fba17b 1097 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1098 delete $conf->{$opt};
68fba17b
AD
1099 next if !$running;
1100 my $netid = $1;
18862537 1101 PVE::Network::veth_delete("veth${vmid}i$netid");
93285df8
DM
1102 } else {
1103 die "implement me"
1104 }
bf0b8c43 1105 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8
DM
1106 }
1107 }
1108
be6383d7
WB
1109 # There's no separate swap size to configure, there's memory and "total"
1110 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1111 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1112 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1113 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1114
1115 $wanted_memory //= ($conf->{memory} || 512);
1116 $wanted_swap //= ($conf->{swap} || 0);
1117
1118 my $total = $wanted_memory + $wanted_swap;
1119 if ($running) {
1120 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1121 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1122 }
27916659
DM
1123 $conf->{memory} = $wanted_memory;
1124 $conf->{swap} = $wanted_swap;
1125
1126 PVE::LXC::write_config($vmid, $conf) if $running;
be6383d7
WB
1127 }
1128
93285df8
DM
1129 foreach my $opt (keys %$param) {
1130 my $value = $param->{$opt};
1131 if ($opt eq 'hostname') {
27916659 1132 $conf->{$opt} = $value;
a99b3509 1133 } elsif ($opt eq 'onboot') {
27916659 1134 $conf->{$opt} = $value ? 1 : 0;
a3249355 1135 } elsif ($opt eq 'startup') {
27916659 1136 $conf->{$opt} = $value;
40603eb3 1137 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
e576f689
DM
1138 $conf->{$opt} = $value;
1139 push @nohotplug, $opt;
1140 next if $running;
ffa1d001 1141 } elsif ($opt eq 'nameserver') {
117636e5 1142 my $list = verify_nameserver_list($value);
27916659 1143 $conf->{$opt} = $list;
bf0b8c43
AD
1144 push @nohotplug, $opt;
1145 next if $running;
ffa1d001 1146 } elsif ($opt eq 'searchdomain') {
117636e5 1147 my $list = verify_searchdomain_list($value);
27916659 1148 $conf->{$opt} = $list;
bf0b8c43
AD
1149 push @nohotplug, $opt;
1150 next if $running;
45573f7c 1151 } elsif ($opt eq 'cpulimit') {
27916659
DM
1152 $conf->{$opt} = $value;
1153 push @nohotplug, $opt; # fixme: hotplug
1154 next;
b80dd50a 1155 } elsif ($opt eq 'cpuunits') {
27916659 1156 $conf->{$opt} = $value;
bf0b8c43 1157 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1158 } elsif ($opt eq 'description') {
27916659 1159 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1160 } elsif ($opt =~ m/^net(\d+)$/) {
1161 my $netid = $1;
a16d94c8 1162 my $net = parse_lxc_network($value);
27916659
DM
1163 if (!$running) {
1164 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1165 } else {
bedeaaf1
AD
1166 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1167 }
93285df8 1168 } else {
a92f66c9 1169 die "implement me: $opt";
93285df8 1170 }
bf0b8c43 1171 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8 1172 }
bf0b8c43 1173
5cfa0567
DM
1174 if ($running && scalar(@nohotplug)) {
1175 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1176 }
93285df8 1177}
c325b32f 1178
6f035afe
DM
1179sub has_dev_console {
1180 my ($conf) = @_;
1181
1182 return !(defined($conf->{console}) && !$conf->{console});
1183}
1184
0d0ca400
DM
1185sub get_tty_count {
1186 my ($conf) = @_;
1187
1188 return $conf->{tty} // $confdesc->{tty}->{default};
1189}
1190
aca816ad
DM
1191sub get_cmode {
1192 my ($conf) = @_;
1193
1194 return $conf->{cmode} // $confdesc->{cmode}->{default};
1195}
1196
1197sub get_console_command {
1198 my ($vmid, $conf) = @_;
1199
1200 my $cmode = get_cmode($conf);
1201
1202 if ($cmode eq 'console') {
1203 return ['lxc-console', '-n', $vmid, '-t', 0];
1204 } elsif ($cmode eq 'tty') {
1205 return ['lxc-console', '-n', $vmid];
1206 } elsif ($cmode eq 'shell') {
1207 return ['lxc-attach', '--clear-env', '-n', $vmid];
1208 } else {
1209 die "internal error";
1210 }
1211}
1212
c325b32f
DM
1213sub get_primary_ips {
1214 my ($conf) = @_;
1215
1216 # return data from net0
cbb03fea 1217
27916659 1218 return undef if !defined($conf->{net0});
a16d94c8 1219 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1220
1221 my $ipv4 = $net->{ip};
db78a181
WB
1222 if ($ipv4) {
1223 if ($ipv4 =~ /^(dhcp|manual)$/) {
1224 $ipv4 = undef
1225 } else {
1226 $ipv4 =~ s!/\d+$!!;
1227 }
1228 }
65e5eaa3 1229 my $ipv6 = $net->{ip6};
db78a181
WB
1230 if ($ipv6) {
1231 if ($ipv6 =~ /^(dhcp|manual)$/) {
1232 $ipv6 = undef;
1233 } else {
1234 $ipv6 =~ s!/\d+$!!;
1235 }
1236 }
cbb03fea 1237
c325b32f
DM
1238 return ($ipv4, $ipv6);
1239}
148d1cb4 1240
ef241384 1241
27916659 1242sub destroy_lxc_container {
148d1cb4
DM
1243 my ($storage_cfg, $vmid, $conf) = @_;
1244
27916659
DM
1245 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1246 if (defined($rootinfo->{volume})) {
1247 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
1248 PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
148d1cb4 1249 }
27916659
DM
1250 rmdir "/var/lib/lxc/$vmid/rootfs";
1251 unlink "/var/lib/lxc/$vmid/config";
1252 rmdir "/var/lib/lxc/$vmid";
1253 destroy_config($vmid);
1254
1255 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1256 #PVE::Tools::run_command($cmd);
148d1cb4 1257}
68fba17b 1258
ef241384 1259sub vm_stop_cleanup {
5fa890f0 1260 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
ef241384
DM
1261
1262 eval {
1263 if (!$keepActive) {
bf9d912c 1264
a8b6b8a7
AD
1265 my $vollist = [];
1266 my $loopdevlist = [];
bf9d912c 1267
a8b6b8a7 1268 PVE::LXC::foreach_mountpoint($conf, sub {
5fa890f0 1269 my ($ms, $mountpoint) = @_;
bf9d912c
AD
1270
1271 my $volid = $mountpoint->{volume};
a8b6b8a7
AD
1272 return if !$volid || $volid =~ m|^/dev/.+|;
1273 push @$vollist, $volid;
1274 push @$loopdevlist, $volid if $ms ne 'rootfs';
1275 });
bf9d912c 1276
a8b6b8a7
AD
1277 PVE::LXC::dettach_loops($storage_cfg, $loopdevlist);
1278 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1279 }
1280 };
1281 warn $@ if $@; # avoid errors - just warn
1282}
1283
93cdbbfb
AD
1284my $safe_num_ne = sub {
1285 my ($a, $b) = @_;
1286
1287 return 0 if !defined($a) && !defined($b);
1288 return 1 if !defined($a);
1289 return 1 if !defined($b);
1290
1291 return $a != $b;
1292};
1293
1294my $safe_string_ne = sub {
1295 my ($a, $b) = @_;
1296
1297 return 0 if !defined($a) && !defined($b);
1298 return 1 if !defined($a);
1299 return 1 if !defined($b);
1300
1301 return $a ne $b;
1302};
1303
1304sub update_net {
bedeaaf1 1305 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1306
18862537
WB
1307 if ($newnet->{type} ne 'veth') {
1308 # for when there are physical interfaces
1309 die "cannot update interface of type $newnet->{type}";
1310 }
1311
1312 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1313 my $eth = $newnet->{name};
1314
18862537
WB
1315 if (my $oldnetcfg = $conf->{$opt}) {
1316 my $oldnet = parse_lxc_network($oldnetcfg);
1317
1318 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1319 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1320
18862537 1321 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1322 delete $conf->{$opt};
1323 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1324
18862537 1325 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1326
18862537
WB
1327 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1328 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1329 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1330
18862537 1331 if ($oldnet->{bridge}) {
bedeaaf1 1332 PVE::Network::tap_unplug($veth);
18862537
WB
1333 foreach (qw(bridge tag firewall)) {
1334 delete $oldnet->{$_};
1335 }
1336 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1
AD
1337 PVE::LXC::write_config($vmid, $conf);
1338 }
93cdbbfb 1339
18862537
WB
1340 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1341 foreach (qw(bridge tag firewall)) {
1342 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1343 }
1344 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1 1345 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1346 }
1347 } else {
18862537 1348 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1349 }
1350
bedeaaf1 1351 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1352}
1353
1354sub hotplug_net {
18862537 1355 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1356
18862537 1357 my $veth = "veth${vmid}i${netid}";
cbb03fea 1358 my $vethpeer = $veth . "p";
93cdbbfb
AD
1359 my $eth = $newnet->{name};
1360
1361 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1362 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1363
cbb03fea 1364 # attach peer in container
93cdbbfb
AD
1365 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1366 PVE::Tools::run_command($cmd);
1367
cbb03fea 1368 # link up peer in container
93cdbbfb
AD
1369 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1370 PVE::Tools::run_command($cmd);
bedeaaf1 1371
18862537
WB
1372 my $done = { type => 'veth' };
1373 foreach (qw(bridge tag firewall hwaddr name)) {
1374 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1375 }
1376 $conf->{$opt} = print_lxc_network($done);
bedeaaf1
AD
1377
1378 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1379}
1380
68a05bb3 1381sub update_ipconfig {
bedeaaf1
AD
1382 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1383
1384 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1385
18862537 1386 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1387 my $deleted = [];
1388 my $added = [];
8d723477
WB
1389 my $nscmd = sub {
1390 my $cmdargs = shift;
1391 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1392 };
8d723477 1393 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1394
84e0c123 1395 my $change_ip_config = sub {
f39002a6
DM
1396 my ($ipversion) = @_;
1397
1398 my $family_opt = "-$ipversion";
1399 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1400 my $gw= "gw$suffix";
1401 my $ip= "ip$suffix";
bedeaaf1 1402
6178b0dd
WB
1403 my $newip = $newnet->{$ip};
1404 my $newgw = $newnet->{$gw};
1405 my $oldip = $optdata->{$ip};
1406
1407 my $change_ip = &$safe_string_ne($oldip, $newip);
1408 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1409
84e0c123 1410 return if !$change_ip && !$change_gw;
68a05bb3 1411
84e0c123 1412 # step 1: add new IP, if this fails we cancel
6178b0dd 1413 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1414 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1415 if (my $err = $@) {
1416 warn $err;
1417 return;
1418 }
bedeaaf1 1419 }
bedeaaf1 1420
84e0c123
WB
1421 # step 2: replace gateway
1422 # If this fails we delete the added IP and cancel.
1423 # If it succeeds we save the config and delete the old IP, ignoring
1424 # errors. The config is then saved.
1425 # Note: 'ip route replace' can add
1426 if ($change_gw) {
6178b0dd 1427 if ($newgw) {
8d723477 1428 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1429 if (my $err = $@) {
1430 warn $err;
1431 # the route was not replaced, the old IP is still available
1432 # rollback (delete new IP) and cancel
1433 if ($change_ip) {
8d723477 1434 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1435 warn $@ if $@; # no need to die here
1436 }
1437 return;
1438 }
1439 } else {
8d723477 1440 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1441 # if the route was not deleted, the guest might have deleted it manually
1442 # warn and continue
1443 warn $@ if $@;
1444 }
2bfd1615 1445 }
2bfd1615 1446
6178b0dd 1447 # from this point on we save the configuration
84e0c123 1448 # step 3: delete old IP ignoring errors
6178b0dd 1449 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1450 # We need to enable promote_secondaries, otherwise our newly added
1451 # address will be removed along with the old one.
1452 my $promote = 0;
1453 eval {
1454 if ($ipversion == 4) {
1455 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1456 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1457 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1458 }
1459 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1460 };
84e0c123 1461 warn $@ if $@; # no need to die here
8d723477
WB
1462
1463 if ($ipversion == 4) {
1464 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1465 }
bedeaaf1
AD
1466 }
1467
84e0c123
WB
1468 foreach my $property ($ip, $gw) {
1469 if ($newnet->{$property}) {
1470 $optdata->{$property} = $newnet->{$property};
1471 } else {
1472 delete $optdata->{$property};
1473 }
bedeaaf1 1474 }
18862537 1475 $conf->{$opt} = print_lxc_network($optdata);
84e0c123
WB
1476 PVE::LXC::write_config($vmid, $conf);
1477 $lxc_setup->setup_network($conf);
1478 };
bedeaaf1 1479
f39002a6
DM
1480 &$change_ip_config(4);
1481 &$change_ip_config(6);
489e960d
WL
1482
1483}
1484
a92f66c9
WL
1485# Internal snapshots
1486
1487# NOTE: Snapshot create/delete involves several non-atomic
1488# action, and can take a long time.
1489# So we try to avoid locking the file and use 'lock' variable
1490# inside the config file instead.
1491
1492my $snapshot_copy_config = sub {
1493 my ($source, $dest) = @_;
1494
1495 foreach my $k (keys %$source) {
1496 next if $k eq 'snapshots';
09d3ec42
DM
1497 next if $k eq 'snapstate';
1498 next if $k eq 'snaptime';
1499 next if $k eq 'vmstate';
1500 next if $k eq 'lock';
a92f66c9 1501 next if $k eq 'digest';
09d3ec42 1502 next if $k eq 'description';
a92f66c9
WL
1503
1504 $dest->{$k} = $source->{$k};
1505 }
1506};
1507
1508my $snapshot_prepare = sub {
1509 my ($vmid, $snapname, $comment) = @_;
1510
1511 my $snap;
1512
1513 my $updatefn = sub {
1514
1515 my $conf = load_config($vmid);
1516
bb1ac2de
DM
1517 die "you can't take a snapshot if it's a template\n"
1518 if is_template($conf);
1519
a92f66c9
WL
1520 check_lock($conf);
1521
09d3ec42 1522 $conf->{lock} = 'snapshot';
a92f66c9
WL
1523
1524 die "snapshot name '$snapname' already used\n"
1525 if defined($conf->{snapshots}->{$snapname});
1526
1527 my $storecfg = PVE::Storage::config();
1528 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1529
1530 $snap = $conf->{snapshots}->{$snapname} = {};
1531
1532 &$snapshot_copy_config($conf, $snap);
1533
09d3ec42
DM
1534 $snap->{'snapstate'} = "prepare";
1535 $snap->{'snaptime'} = time();
1536 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1537 $conf->{snapshots}->{$snapname} = $snap;
1538
1539 PVE::LXC::write_config($vmid, $conf);
1540 };
1541
1542 lock_container($vmid, 10, $updatefn);
1543
1544 return $snap;
1545};
1546
1547my $snapshot_commit = sub {
1548 my ($vmid, $snapname) = @_;
1549
1550 my $updatefn = sub {
1551
1552 my $conf = load_config($vmid);
1553
1554 die "missing snapshot lock\n"
09d3ec42 1555 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1556
27916659 1557 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1558 if !defined($conf->{snapshots}->{$snapname});
1559
1560 die "wrong snapshot state\n"
09d3ec42
DM
1561 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1562 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1563
09d3ec42
DM
1564 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1565 delete $conf->{lock};
1566 $conf->{parent} = $snapname;
a92f66c9
WL
1567
1568 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1569 };
1570
1571 lock_container($vmid, 10 ,$updatefn);
1572};
1573
1574sub has_feature {
1575 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1576
a92f66c9
WL
1577 #Fixme add other drives if necessary.
1578 my $err;
09d3ec42
DM
1579
1580 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1581 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1582
1583 return $err ? 0 : 1;
1584}
1585
489e960d
WL
1586sub snapshot_create {
1587 my ($vmid, $snapname, $comment) = @_;
1588
a92f66c9
WL
1589 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1590
09d3ec42 1591 my $conf = load_config($vmid);
a92f66c9
WL
1592
1593 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1594 my $running = check_running($vmid);
1595 eval {
1596 if ($running) {
1597 PVE::Tools::run_command($cmd);
1598 };
1599
1600 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1601 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1602 my $volid = $rootinfo->{volume};
a92f66c9
WL
1603
1604 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1605 if ($running) {
1606 PVE::Tools::run_command($cmd);
1607 };
489e960d 1608
a92f66c9
WL
1609 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1610 &$snapshot_commit($vmid, $snapname);
1611 };
1612 if(my $err = $@) {
31429832 1613 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1614 die "$err\n";
1615 }
68a05bb3
AD
1616}
1617
57ccb3f8
WL
1618sub snapshot_delete {
1619 my ($vmid, $snapname, $force) = @_;
1620
31429832
WL
1621 my $snap;
1622
1623 my $conf;
1624
1625 my $updatefn = sub {
1626
1627 $conf = load_config($vmid);
1628
bb1ac2de
DM
1629 die "you can't delete a snapshot if vm is a template\n"
1630 if is_template($conf);
1631
31429832
WL
1632 $snap = $conf->{snapshots}->{$snapname};
1633
1634 check_lock($conf);
1635
1636 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1637
09d3ec42 1638 $snap->{snapstate} = 'delete';
31429832
WL
1639
1640 PVE::LXC::write_config($vmid, $conf);
1641 };
1642
1643 lock_container($vmid, 10, $updatefn);
1644
1645 my $storecfg = PVE::Storage::config();
1646
1647 my $del_snap = sub {
1648
1649 check_lock($conf);
1650
09d3ec42
DM
1651 if ($conf->{parent} eq $snapname) {
1652 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1653 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1654 } else {
09d3ec42 1655 delete $conf->{parent};
31429832
WL
1656 }
1657 }
1658
1659 delete $conf->{snapshots}->{$snapname};
1660
1661 PVE::LXC::write_config($vmid, $conf);
1662 };
1663
09d3ec42
DM
1664 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1665 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1666 my $volid = $rootinfo->{volume};
31429832
WL
1667
1668 eval {
1669 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1670 };
1671 my $err = $@;
1672
1673 if(!$err || ($err && $force)) {
1674 lock_container($vmid, 10, $del_snap);
1675 if ($err) {
1676 die "Can't delete snapshot: $vmid $snapname $err\n";
1677 }
1678 }
57ccb3f8
WL
1679}
1680
723157f6
WL
1681sub snapshot_rollback {
1682 my ($vmid, $snapname) = @_;
1683
6860ba0c
WL
1684 my $storecfg = PVE::Storage::config();
1685
1686 my $conf = load_config($vmid);
1687
bb1ac2de
DM
1688 die "you can't rollback if vm is a template\n" if is_template($conf);
1689
6860ba0c
WL
1690 my $snap = $conf->{snapshots}->{$snapname};
1691
1692 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1693
09d3ec42
DM
1694 my $rootfs = $snap->{rootfs};
1695 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1696 my $volid = $rootinfo->{volume};
1697
1698 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1699
1700 my $updatefn = sub {
1701
09d3ec42
DM
1702 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1703 if $snap->{snapstate};
6860ba0c
WL
1704
1705 check_lock($conf);
6860ba0c 1706
b935932a 1707 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1708
1709 die "unable to rollback vm $vmid: vm is running\n"
1710 if check_running($vmid);
1711
09d3ec42 1712 $conf->{lock} = 'rollback';
6860ba0c
WL
1713
1714 my $forcemachine;
1715
1716 # copy snapshot config to current config
1717
1718 my $tmp_conf = $conf;
1719 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1720 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1721 delete $conf->{snaptime};
1722 delete $conf->{snapname};
1723 $conf->{parent} = $snapname;
6860ba0c
WL
1724
1725 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1726 };
1727
1728 my $unlockfn = sub {
09d3ec42 1729 delete $conf->{lock};
6860ba0c
WL
1730 PVE::LXC::write_config($vmid, $conf);
1731 };
1732
1733 lock_container($vmid, 10, $updatefn);
1734
09d3ec42 1735 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1736
1737 lock_container($vmid, 5, $unlockfn);
723157f6 1738}
b935932a 1739
bb1ac2de
DM
1740sub template_create {
1741 my ($vmid, $conf) = @_;
1742
1743 my $storecfg = PVE::Storage::config();
1744
1745 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1746 my $volid = $rootinfo->{volume};
1747
1748 die "Template feature is not available for '$volid'\n"
1749 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1750
1751 PVE::Storage::activate_volumes($storecfg, [$volid]);
1752
1753 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1754 $rootinfo->{volume} = $template_volid;
dde7b02b 1755 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
bb1ac2de
DM
1756
1757 write_config($vmid, $conf);
1758}
1759
1760sub is_template {
1761 my ($conf) = @_;
1762
1763 return 1 if defined $conf->{template} && $conf->{template} == 1;
1764}
1765
ced7fddb
AD
1766sub foreach_mountpoint {
1767 my ($conf, $func) = @_;
1768
eaebef36
DM
1769 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
1770 $mountpoint->{mp} = '/'; # just to be sure
1771 &$func('rootfs', $mountpoint);
1772
1773 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1774 my $key = "mp$i";
1775 next if !defined($conf->{$key});
1776 $mountpoint = parse_ct_mountpoint($conf->{$key});
1777 &$func($key, $mountpoint);
ced7fddb
AD
1778 }
1779}
1780
6d89c14d
AD
1781sub loopdevices_list {
1782
1783 my $loopdev = {};
1784 my $parser = sub {
1785 my $line = shift;
1786 if ($line =~ m/^(\/dev\/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1787 $loopdev->{$1} = $2;
1788 }
1789 };
1790
1791 PVE::Tools::run_command(['losetup'], outfunc => $parser);
1792
1793 return $loopdev;
1794}
54304b66
AD
1795
1796sub blockdevices_list {
1797
1798 my $bdevs = {};
1799 dir_glob_foreach("/sys/dev/block/", '(\d+):(\d+)', sub {
1800 my (undef, $major, $minor) = @_;
1801 my $bdev = readlink("/sys/dev/block/$major:$minor");
1802 $bdev =~ s/\.\.\/\.\.\/devices\/virtual\/block\//\/dev\//;
1803 $bdevs->{$bdev}->{major} = $major;
1804 $bdevs->{$bdev}->{minor} = $minor;
1805 });
1806 return $bdevs;
1807}
1808
6b33ba58
AD
1809sub find_loopdev {
1810 my ($loopdevs, $path) = @_;
1811
1812 foreach my $dev (keys %$loopdevs){
1813 return $dev if $loopdevs->{$dev} eq $path;
1814 }
1815}
f5635601 1816
52389a07
DM
1817sub check_ct_modify_config_perm {
1818 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1819
1820 return 1 if $authuser ne 'root@pam';
1821
1822 foreach my $opt (@$key_list) {
1823
1824 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1825 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1826 } elsif ($opt eq 'disk') {
1827 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1828 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1829 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1830 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1831 $opt eq 'searchdomain' || $opt eq 'hostname') {
1832 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1833 } else {
1834 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1835 }
1836 }
1837
1838 return 1;
1839}
1840
bafcd6d4
AD
1841
1842sub volid_path {
a3076d81 1843 my ($volid, $storage_cfg, $loopdevs) = @_;
bafcd6d4
AD
1844
1845 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1846 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1847 my $path = PVE::Storage::path($storage_cfg, $volid);
1848
1849 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1850 PVE::Storage::parse_volname($storage_cfg, $volid);
1851
1852 die "unable to use template as mountpoint\n" if $isBase;
1853
1854 if ($format eq 'subvol') {
1855 #do nothing
1856 } elsif ($format eq 'raw') {
1857
1858 if ($scfg->{path}) {
a3076d81 1859 $path = PVE::LXC::find_loopdev($loopdevs, $path) if $loopdevs;
bafcd6d4
AD
1860 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1861 #do nothing
1862 } else {
1863 die "unsupported storage type '$scfg->{type}'\n";
1864 }
1865 } else {
1866 die "unsupported image format '$format'\n";
1867 }
1868
1869 return $path;
1870
1871}
571b33da
AD
1872
1873sub attach_loops {
1874 my ($storage_cfg, $vollist) = @_;
1875
1876 my $loopdevs = {};
1877
1878 foreach my $volid (@$vollist) {
1879
1880 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1881 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1882
1883 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1884 PVE::Storage::parse_volname($storage_cfg, $volid);
1885
35f92a09
DM
1886 if (($format ne 'subvol') &&
1887 ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
571b33da
AD
1888 my $path = PVE::Storage::path($storage_cfg, $volid);
1889 my $loopdev;
1890
1891 my $parser = sub {
1892 my $line = shift;
1893 $loopdev = $line if $line =~m|^/dev/loop\d+$|;
1894 $loopdevs->{$loopdev} = $path;
1895 };
1896
1897 PVE::Tools::run_command(['losetup', '--find', '--show', $path], outfunc => $parser);
1898 }
1899 }
35f92a09 1900
571b33da
AD
1901 return $loopdevs;
1902}
1903
a8b6b8a7
AD
1904sub dettach_loops {
1905 my ($storage_cfg, $vollist) = @_;
1906
1907 my $loopdevs = loopdevices_list();
1908
1909 foreach my $volid (@$vollist) {
1910
1911 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1912 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1913
1914 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1915 PVE::Storage::parse_volname($storage_cfg, $volid);
1916
1917 if($format ne 'subvol' && ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
1918 my $path = PVE::Storage::path($storage_cfg, $volid);
1919 foreach my $dev (keys %$loopdevs){
1920 PVE::Tools::run_command(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1921 }
1922 }
1923 }
1924}
1925
cc6b0307
AD
1926
1927sub mountpoint_mount {
1e8f01be 1928 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs) = @_;
cc6b0307
AD
1929
1930 my $volid = $mountpoint->{volume};
1931 my $mount = $mountpoint->{mp};
1932
1933 return if !$volid || !$mount;
1934
1935 eval {
35f92a09
DM
1936 $rootdir =~ s!/+$!!;
1937 my $mount_path = "$rootdir/$mount";
cc6b0307
AD
1938 File::Path::mkpath($mount_path);
1939
1940 if ($volid =~ m|^/dev/.+|) {
1941 PVE::Tools::run_command(['mount', $volid, $mount_path]);
1942 return;
1943 }
1944
a3076d81 1945 my $path = PVE::LXC::volid_path($volid, $storage_cfg, $loopdevs);
cc6b0307
AD
1946
1947 if ($path !~ m|^/dev/.+|) {
1948 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
1949 return;
1950 }
1951
1952 PVE::Tools::run_command(['mount', $path, $mount_path]);
1953 };
1954 warn $@ if $@;
1955}
1956
f76a2828 19571;