]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
create: do not pass mointpoints to update_pct_config, simplify restore logic
[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 441sub load_config {
d18499cf 442 my ($vmid, $node) = @_;
f76a2828 443
d18499cf
TL
444 $node = $nodename if !$node;
445 my $cfspath = cfs_config_path($vmid, $node);
f76a2828
DM
446
447 my $conf = PVE::Cluster::cfs_read_file($cfspath);
448 die "container $vmid does not exists\n" if !defined($conf);
449
450 return $conf;
451}
452
5b4657d0
DM
453sub create_config {
454 my ($vmid, $conf) = @_;
455
456 my $dir = "/etc/pve/nodes/$nodename/lxc";
457 mkdir $dir;
458
5b4657d0
DM
459 write_config($vmid, $conf);
460}
461
462sub destroy_config {
463 my ($vmid) = @_;
464
27916659 465 unlink config_file($vmid, $nodename);
5b4657d0
DM
466}
467
f76a2828
DM
468sub write_config {
469 my ($vmid, $conf) = @_;
470
471 my $cfspath = cfs_config_path($vmid);
472
473 PVE::Cluster::cfs_write_file($cfspath, $conf);
474}
475
d14a9a1b
DM
476# flock: we use one file handle per process, so lock file
477# can be called multiple times and succeeds for the same process.
478
479my $lock_handles = {};
480my $lockdir = "/run/lock/lxc";
481
482sub lock_filename {
483 my ($vmid) = @_;
cbb03fea 484
d14a9a1b
DM
485 return "$lockdir/pve-config-{$vmid}.lock";
486}
487
488sub lock_aquire {
489 my ($vmid, $timeout) = @_;
490
491 $timeout = 10 if !$timeout;
492 my $mode = LOCK_EX;
493
494 my $filename = lock_filename($vmid);
495
f99e8278
AD
496 mkdir $lockdir if !-d $lockdir;
497
d14a9a1b
DM
498 my $lock_func = sub {
499 if (!$lock_handles->{$$}->{$filename}) {
500 my $fh = new IO::File(">>$filename") ||
501 die "can't open file - $!\n";
502 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
503 }
504
505 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
506 print STDERR "trying to aquire lock...";
507 my $success;
508 while(1) {
509 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
510 # try again on EINTR (see bug #273)
511 if ($success || ($! != EINTR)) {
512 last;
513 }
514 }
515 if (!$success) {
516 print STDERR " failed\n";
517 die "can't aquire lock - $!\n";
518 }
519
520 $lock_handles->{$$}->{$filename}->{refcount}++;
cbb03fea 521
d14a9a1b
DM
522 print STDERR " OK\n";
523 }
524 };
525
526 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
527 my $err = $@;
528 if ($err) {
529 die "can't lock file '$filename' - $err";
cbb03fea 530 }
d14a9a1b
DM
531}
532
533sub lock_release {
534 my ($vmid) = @_;
535
536 my $filename = lock_filename($vmid);
537
538 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
539 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
540 if ($refcount <= 0) {
541 $lock_handles->{$$}->{$filename} = undef;
542 close ($fh);
543 }
544 }
545}
546
f76a2828
DM
547sub lock_container {
548 my ($vmid, $timeout, $code, @param) = @_;
549
d14a9a1b 550 my $res;
f76a2828 551
d14a9a1b
DM
552 lock_aquire($vmid, $timeout);
553 eval { $res = &$code(@param) };
554 my $err = $@;
555 lock_release($vmid);
f76a2828 556
d14a9a1b 557 die $err if $err;
f76a2828
DM
558
559 return $res;
560}
561
ec52ac21
DM
562sub option_exists {
563 my ($name) = @_;
564
565 return defined($confdesc->{$name});
566}
f76a2828
DM
567
568# add JSON properties for create and set function
569sub json_config_properties {
570 my $prop = shift;
571
572 foreach my $opt (keys %$confdesc) {
09d3ec42 573 next if $opt eq 'parent' || $opt eq 'snaptime';
27916659
DM
574 next if $prop->{$opt};
575 $prop->{$opt} = $confdesc->{$opt};
576 }
577
578 return $prop;
579}
580
581sub json_config_properties_no_rootfs {
582 my $prop = shift;
583
584 foreach my $opt (keys %$confdesc) {
585 next if $prop->{$opt};
09d3ec42 586 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
f76a2828
DM
587 $prop->{$opt} = $confdesc->{$opt};
588 }
589
590 return $prop;
591}
592
822de0c3
DM
593# container status helpers
594
595sub list_active_containers {
cbb03fea 596
822de0c3
DM
597 my $filename = "/proc/net/unix";
598
599 # similar test is used by lcxcontainers.c: list_active_containers
600 my $res = {};
cbb03fea 601
822de0c3
DM
602 my $fh = IO::File->new ($filename, "r");
603 return $res if !$fh;
604
605 while (defined(my $line = <$fh>)) {
606 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
607 my $path = $1;
27916659 608 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
822de0c3
DM
609 $res->{$1} = 1;
610 }
611 }
612 }
613
614 close($fh);
cbb03fea 615
822de0c3
DM
616 return $res;
617}
f76a2828 618
5c752bbf
DM
619# warning: this is slow
620sub check_running {
621 my ($vmid) = @_;
622
623 my $active_hash = list_active_containers();
624
625 return 1 if defined($active_hash->{$vmid});
cbb03fea 626
5c752bbf
DM
627 return undef;
628}
629
10fc3ba5
DM
630sub get_container_disk_usage {
631 my ($vmid) = @_;
632
633 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
cbb03fea 634
10fc3ba5
DM
635 my $res = {
636 total => 0,
637 used => 0,
638 avail => 0,
639 };
640
641 my $parser = sub {
642 my $line = shift;
643 if (my ($fsid, $total, $used, $avail) = $line =~
644 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
645 $res = {
646 total => $total,
647 used => $used,
648 avail => $avail,
649 };
650 }
651 };
652 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
653 warn $@ if $@;
654
655 return $res;
656}
657
f76a2828
DM
658sub vmstatus {
659 my ($opt_vmid) = @_;
660
661 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
662
822de0c3 663 my $active_hash = list_active_containers();
cbb03fea 664
f76a2828 665 foreach my $vmid (keys %$list) {
f76a2828 666 my $d = $list->{$vmid};
10fc3ba5
DM
667
668 my $running = defined($active_hash->{$vmid});
cbb03fea 669
10fc3ba5 670 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
671
672 my $cfspath = cfs_config_path($vmid);
238a56cb 673 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
cbb03fea 674
27916659 675 $d->{name} = $conf->{'hostname'} || "CT$vmid";
238a56cb 676 $d->{name} =~ s/[\s]//g;
cbb03fea 677
27916659 678 $d->{cpus} = $conf->{cpulimit} // 0;
44da0641 679
27916659
DM
680 if ($running) {
681 my $res = get_container_disk_usage($vmid);
682 $d->{disk} = $res->{used};
683 $d->{maxdisk} = $res->{total};
684 } else {
685 $d->{disk} = 0;
686 # use 4GB by default ??
687 if (my $rootfs = $conf->{rootfs}) {
688 my $rootinfo = parse_ct_mountpoint($rootfs);
689 $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
690 } else {
691 $d->{maxdisk} = 4*1024*1024*1024;
10fc3ba5 692 }
238a56cb 693 }
cbb03fea 694
238a56cb
DM
695 $d->{mem} = 0;
696 $d->{swap} = 0;
95df9a12
DM
697 $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
698 $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
e901d418 699
238a56cb
DM
700 $d->{uptime} = 0;
701 $d->{cpu} = 0;
e901d418 702
238a56cb
DM
703 $d->{netout} = 0;
704 $d->{netin} = 0;
f76a2828 705
238a56cb
DM
706 $d->{diskread} = 0;
707 $d->{diskwrite} = 0;
bb1ac2de
DM
708
709 $d->{template} = is_template($conf);
f76a2828 710 }
cbb03fea 711
238a56cb
DM
712 foreach my $vmid (keys %$list) {
713 my $d = $list->{$vmid};
714 next if $d->{status} ne 'running';
f76a2828 715
22a77285
DM
716 $d->{uptime} = 100; # fixme:
717
238a56cb
DM
718 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
719 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
b5289322
AD
720
721 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
1e647c7c 722 my @bytes = split(/\n/, $blkio_bytes);
b5289322 723 foreach my $byte (@bytes) {
1e647c7c
DM
724 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
725 $d->{diskread} = $2 if $key eq 'Read';
726 $d->{diskwrite} = $2 if $key eq 'Write';
727 }
b5289322 728 }
238a56cb 729 }
cbb03fea 730
f76a2828
DM
731 return $list;
732}
733
27916659
DM
734my $parse_size = sub {
735 my ($value) = @_;
736
737 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
738 my ($size, $unit) = ($1, $3);
739 if ($unit) {
740 if ($unit eq 'K') {
741 $size = $size * 1024;
742 } elsif ($unit eq 'M') {
743 $size = $size * 1024 * 1024;
744 } elsif ($unit eq 'G') {
745 $size = $size * 1024 * 1024 * 1024;
746 }
747 }
748 return int($size);
749};
750
751sub parse_ct_mountpoint {
752 my ($data) = @_;
753
754 $data //= '';
755
756 my $res = {};
757
758 foreach my $p (split (/,/, $data)) {
759 next if $p =~ m/^\s*$/;
760
02c9d10c 761 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
27916659
DM
762 my ($k, $v) = ($1, $2);
763 return undef if defined($res->{$k});
dada5f33 764 $res->{$k} = $v;
27916659
DM
765 } else {
766 if (!$res->{volume} && $p !~ m/=/) {
767 $res->{volume} = $p;
768 } else {
769 return undef;
770 }
771 }
772 }
773
774 return undef if !$res->{volume};
775
776 return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
777
778 if ($res->{size}) {
779 return undef if !defined($res->{size} = &$parse_size($res->{size}));
780 }
781
782 return $res;
783}
7dfc49cc 784
dde7b02b 785sub print_ct_mountpoint {
bb1ac2de
DM
786 my ($info) = @_;
787
788 my $opts = '';
789
790 die "missing volume\n" if !$info->{volume};
791
792 foreach my $o ('size', 'backup') {
7092c9f1 793 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
bb1ac2de
DM
794 }
795
796 return "$info->{volume}$opts";
797}
798
7dfc49cc 799sub print_lxc_network {
f76a2828
DM
800 my $net = shift;
801
bedeaaf1 802 die "no network name defined\n" if !$net->{name};
f76a2828 803
bedeaaf1 804 my $res = "name=$net->{name}";
7dfc49cc 805
bedeaaf1 806 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
807 next if !defined($net->{$k});
808 $res .= ",$k=$net->{$k}";
809 }
7dfc49cc 810
f76a2828
DM
811 return $res;
812}
813
7dfc49cc
DM
814sub parse_lxc_network {
815 my ($data) = @_;
816
817 my $res = {};
818
819 return $res if !$data;
820
821 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 822 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
823 $res->{$1} = $2;
824 } else {
825 return undef;
826 }
827 }
828
829 $res->{type} = 'veth';
93cdbbfb 830 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 831
7dfc49cc
DM
832 return $res;
833}
f76a2828 834
238a56cb
DM
835sub read_cgroup_value {
836 my ($group, $vmid, $name, $full) = @_;
837
838 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
839
840 return PVE::Tools::file_get_contents($path) if $full;
841
842 return PVE::Tools::file_read_firstline($path);
843}
844
bf0b8c43
AD
845sub write_cgroup_value {
846 my ($group, $vmid, $name, $value) = @_;
847
848 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
849 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
850
851}
852
52f1d76b
DM
853sub find_lxc_console_pids {
854
855 my $res = {};
856
857 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
858 my ($pid) = @_;
859
860 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
861 return if !$cmdline;
862
863 my @args = split(/\0/, $cmdline);
864
865 # serach for lxc-console -n <vmid>
cbb03fea 866 return if scalar(@args) != 3;
52f1d76b
DM
867 return if $args[1] ne '-n';
868 return if $args[2] !~ m/^\d+$/;
869 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 870
52f1d76b 871 my $vmid = $args[2];
cbb03fea 872
52f1d76b
DM
873 push @{$res->{$vmid}}, $pid;
874 });
875
876 return $res;
877}
878
bedeaaf1
AD
879sub find_lxc_pid {
880 my ($vmid) = @_;
881
882 my $pid = undef;
883 my $parser = sub {
884 my $line = shift;
8b25977f 885 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1
AD
886 };
887 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
888
8b25977f 889 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 890
8b25977f 891 return $pid;
bedeaaf1
AD
892}
893
55fa4e09
DM
894my $ipv4_reverse_mask = [
895 '0.0.0.0',
896 '128.0.0.0',
897 '192.0.0.0',
898 '224.0.0.0',
899 '240.0.0.0',
900 '248.0.0.0',
901 '252.0.0.0',
902 '254.0.0.0',
903 '255.0.0.0',
904 '255.128.0.0',
905 '255.192.0.0',
906 '255.224.0.0',
907 '255.240.0.0',
908 '255.248.0.0',
909 '255.252.0.0',
910 '255.254.0.0',
911 '255.255.0.0',
912 '255.255.128.0',
913 '255.255.192.0',
914 '255.255.224.0',
915 '255.255.240.0',
916 '255.255.248.0',
917 '255.255.252.0',
918 '255.255.254.0',
919 '255.255.255.0',
920 '255.255.255.128',
921 '255.255.255.192',
922 '255.255.255.224',
923 '255.255.255.240',
924 '255.255.255.248',
925 '255.255.255.252',
926 '255.255.255.254',
927 '255.255.255.255',
928];
cbb03fea
DM
929
930# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
931# CIDR networks
932sub parse_ipv4_cidr {
933 my ($cidr, $noerr) = @_;
934
935 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
936 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
937 }
cbb03fea 938
55fa4e09 939 return undef if $noerr;
cbb03fea 940
55fa4e09
DM
941 die "unable to parse ipv4 address/mask\n";
942}
93285df8 943
a12a36e0
WL
944sub check_lock {
945 my ($conf) = @_;
946
27916659 947 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
a12a36e0
WL
948}
949
27916659 950sub update_lxc_config {
c628ffa1 951 my ($storage_cfg, $vmid, $conf) = @_;
b80dd50a 952
bb1ac2de
DM
953 my $dir = "/var/lib/lxc/$vmid";
954
955 if ($conf->{template}) {
956
957 unlink "$dir/config";
958
959 return;
960 }
961
27916659 962 my $raw = '';
b80dd50a 963
27916659
DM
964 die "missing 'arch' - internal error" if !$conf->{arch};
965 $raw .= "lxc.arch = $conf->{arch}\n";
b80dd50a 966
27916659 967 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
c1d32b55 968 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
27916659
DM
969 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
970 } else {
971 die "implement me";
972 }
b80dd50a 973
6f035afe 974 if (!has_dev_console($conf)) {
eeaea429
DM
975 $raw .= "lxc.console = none\n";
976 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
977 }
4f958489 978
0d0ca400 979 my $ttycount = get_tty_count($conf);
27916659 980 $raw .= "lxc.tty = $ttycount\n";
cbb03fea 981
27916659
DM
982 my $utsname = $conf->{hostname} || "CT$vmid";
983 $raw .= "lxc.utsname = $utsname\n";
cbb03fea 984
27916659
DM
985 my $memory = $conf->{memory} || 512;
986 my $swap = $conf->{swap} // 0;
987
988 my $lxcmem = int($memory*1024*1024);
989 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
a12a36e0 990
27916659
DM
991 my $lxcswap = int(($memory + $swap)*1024*1024);
992 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
993
994 if (my $cpulimit = $conf->{cpulimit}) {
995 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
996 my $value = int(100000*$cpulimit);
997 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
a12a36e0
WL
998 }
999
27916659
DM
1000 my $shares = $conf->{cpuunits} || 1024;
1001 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1002
21be9680
AD
1003 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
1004 my $volid = $mountpoint->{volume};
1005 my $path = volid_path ($volid, $storage_cfg);
1006 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
c628ffa1 1007
21be9680 1008 if ($storage) {
a3076d81 1009 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
21be9680
AD
1010 $path = "loop:$path" if $scfg->{path};
1011 }
a3076d81 1012
21be9680 1013 $raw .= "lxc.rootfs = $path\n";
27916659
DM
1014
1015 my $netcount = 0;
1016 foreach my $k (keys %$conf) {
1017 next if $k !~ m/^net(\d+)$/;
1018 my $ind = $1;
a16d94c8 1019 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1020 $netcount++;
1021 $raw .= "lxc.network.type = veth\n";
18862537 1022 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1023 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1024 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1025 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1026 }
1027
e576f689
DM
1028 if (my $lxcconf = $conf->{lxc}) {
1029 foreach my $entry (@$lxcconf) {
1030 my ($k, $v) = @$entry;
1031 $netcount++ if $k eq 'lxc.network.type';
1032 $raw .= "$k = $v\n";
1033 }
1034 }
27916659 1035
e576f689
DM
1036 $raw .= "lxc.network.type = empty\n" if !$netcount;
1037
27916659
DM
1038 File::Path::mkpath("$dir/rootfs");
1039
1040 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1041}
1042
117636e5
DM
1043# verify and cleanup nameserver list (replace \0 with ' ')
1044sub verify_nameserver_list {
1045 my ($nameserver_list) = @_;
1046
1047 my @list = ();
1048 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1049 PVE::JSONSchema::pve_verify_ip($server);
1050 push @list, $server;
1051 }
1052
1053 return join(' ', @list);
1054}
1055
1056sub verify_searchdomain_list {
1057 my ($searchdomain_list) = @_;
1058
1059 my @list = ();
1060 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1061 # todo: should we add checks for valid dns domains?
1062 push @list, $server;
1063 }
1064
1065 return join(' ', @list);
1066}
1067
27916659 1068sub update_pct_config {
93285df8
DM
1069 my ($vmid, $conf, $running, $param, $delete) = @_;
1070
bf0b8c43
AD
1071 my @nohotplug;
1072
cbb03fea
DM
1073 my $rootdir;
1074 if ($running) {
bedeaaf1 1075 my $pid = find_lxc_pid($vmid);
cbb03fea 1076 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1077 }
1078
93285df8
DM
1079 if (defined($delete)) {
1080 foreach my $opt (@$delete) {
27916659 1081 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1082 die "unable to delete required option '$opt'\n";
1083 } elsif ($opt eq 'swap') {
27916659 1084 delete $conf->{$opt};
bf0b8c43 1085 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1086 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1087 delete $conf->{$opt};
4f958489 1088 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1089 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
27916659 1090 delete $conf->{$opt};
bf0b8c43
AD
1091 push @nohotplug, $opt;
1092 next if $running;
68fba17b 1093 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1094 delete $conf->{$opt};
68fba17b
AD
1095 next if !$running;
1096 my $netid = $1;
18862537 1097 PVE::Network::veth_delete("veth${vmid}i$netid");
eb35f9c0 1098 } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
b51a98d4 1099 die "implement me"
93285df8
DM
1100 } else {
1101 die "implement me"
1102 }
bf0b8c43 1103 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8
DM
1104 }
1105 }
1106
be6383d7
WB
1107 # There's no separate swap size to configure, there's memory and "total"
1108 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1109 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1110 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1111 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1112
1113 $wanted_memory //= ($conf->{memory} || 512);
1114 $wanted_swap //= ($conf->{swap} || 0);
1115
1116 my $total = $wanted_memory + $wanted_swap;
1117 if ($running) {
1118 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1119 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1120 }
27916659
DM
1121 $conf->{memory} = $wanted_memory;
1122 $conf->{swap} = $wanted_swap;
1123
1124 PVE::LXC::write_config($vmid, $conf) if $running;
be6383d7
WB
1125 }
1126
93285df8
DM
1127 foreach my $opt (keys %$param) {
1128 my $value = $param->{$opt};
1129 if ($opt eq 'hostname') {
27916659 1130 $conf->{$opt} = $value;
a99b3509 1131 } elsif ($opt eq 'onboot') {
27916659 1132 $conf->{$opt} = $value ? 1 : 0;
a3249355 1133 } elsif ($opt eq 'startup') {
27916659 1134 $conf->{$opt} = $value;
40603eb3 1135 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
e576f689
DM
1136 $conf->{$opt} = $value;
1137 push @nohotplug, $opt;
1138 next if $running;
ffa1d001 1139 } elsif ($opt eq 'nameserver') {
117636e5 1140 my $list = verify_nameserver_list($value);
27916659 1141 $conf->{$opt} = $list;
bf0b8c43
AD
1142 push @nohotplug, $opt;
1143 next if $running;
ffa1d001 1144 } elsif ($opt eq 'searchdomain') {
117636e5 1145 my $list = verify_searchdomain_list($value);
27916659 1146 $conf->{$opt} = $list;
bf0b8c43
AD
1147 push @nohotplug, $opt;
1148 next if $running;
45573f7c 1149 } elsif ($opt eq 'cpulimit') {
27916659
DM
1150 $conf->{$opt} = $value;
1151 push @nohotplug, $opt; # fixme: hotplug
1152 next;
b80dd50a 1153 } elsif ($opt eq 'cpuunits') {
27916659 1154 $conf->{$opt} = $value;
bf0b8c43 1155 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1156 } elsif ($opt eq 'description') {
27916659 1157 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1158 } elsif ($opt =~ m/^net(\d+)$/) {
1159 my $netid = $1;
a16d94c8 1160 my $net = parse_lxc_network($value);
27916659
DM
1161 if (!$running) {
1162 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1163 } else {
bedeaaf1
AD
1164 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1165 }
eb35f9c0 1166 } elsif ($opt eq 'rootfs' || $opt =~ m/^mp(\d+)$/) {
b51a98d4 1167 die "implement me: $opt";
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
09aa32fd
AD
1265 my $vollist = get_vm_volumes($conf);
1266 my $loopdevlist = get_vm_volumes($conf, 'rootfs');
bf9d912c 1267
a8b6b8a7
AD
1268 PVE::LXC::dettach_loops($storage_cfg, $loopdevlist);
1269 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1270 }
1271 };
1272 warn $@ if $@; # avoid errors - just warn
1273}
1274
93cdbbfb
AD
1275my $safe_num_ne = sub {
1276 my ($a, $b) = @_;
1277
1278 return 0 if !defined($a) && !defined($b);
1279 return 1 if !defined($a);
1280 return 1 if !defined($b);
1281
1282 return $a != $b;
1283};
1284
1285my $safe_string_ne = sub {
1286 my ($a, $b) = @_;
1287
1288 return 0 if !defined($a) && !defined($b);
1289 return 1 if !defined($a);
1290 return 1 if !defined($b);
1291
1292 return $a ne $b;
1293};
1294
1295sub update_net {
bedeaaf1 1296 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1297
18862537
WB
1298 if ($newnet->{type} ne 'veth') {
1299 # for when there are physical interfaces
1300 die "cannot update interface of type $newnet->{type}";
1301 }
1302
1303 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1304 my $eth = $newnet->{name};
1305
18862537
WB
1306 if (my $oldnetcfg = $conf->{$opt}) {
1307 my $oldnet = parse_lxc_network($oldnetcfg);
1308
1309 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1310 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1311
18862537 1312 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1313 delete $conf->{$opt};
1314 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1315
18862537 1316 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1317
18862537
WB
1318 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1319 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1320 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1321
18862537 1322 if ($oldnet->{bridge}) {
bedeaaf1 1323 PVE::Network::tap_unplug($veth);
18862537
WB
1324 foreach (qw(bridge tag firewall)) {
1325 delete $oldnet->{$_};
1326 }
1327 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1
AD
1328 PVE::LXC::write_config($vmid, $conf);
1329 }
93cdbbfb 1330
18862537
WB
1331 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1332 foreach (qw(bridge tag firewall)) {
1333 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1334 }
1335 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1 1336 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1337 }
1338 } else {
18862537 1339 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1340 }
1341
bedeaaf1 1342 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1343}
1344
1345sub hotplug_net {
18862537 1346 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1347
18862537 1348 my $veth = "veth${vmid}i${netid}";
cbb03fea 1349 my $vethpeer = $veth . "p";
93cdbbfb
AD
1350 my $eth = $newnet->{name};
1351
1352 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1353 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1354
cbb03fea 1355 # attach peer in container
93cdbbfb
AD
1356 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1357 PVE::Tools::run_command($cmd);
1358
cbb03fea 1359 # link up peer in container
93cdbbfb
AD
1360 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1361 PVE::Tools::run_command($cmd);
bedeaaf1 1362
18862537
WB
1363 my $done = { type => 'veth' };
1364 foreach (qw(bridge tag firewall hwaddr name)) {
1365 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1366 }
1367 $conf->{$opt} = print_lxc_network($done);
bedeaaf1
AD
1368
1369 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1370}
1371
68a05bb3 1372sub update_ipconfig {
bedeaaf1
AD
1373 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1374
f2104b80 1375 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
bedeaaf1 1376
18862537 1377 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1378 my $deleted = [];
1379 my $added = [];
8d723477
WB
1380 my $nscmd = sub {
1381 my $cmdargs = shift;
1382 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1383 };
8d723477 1384 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1385
84e0c123 1386 my $change_ip_config = sub {
f39002a6
DM
1387 my ($ipversion) = @_;
1388
1389 my $family_opt = "-$ipversion";
1390 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1391 my $gw= "gw$suffix";
1392 my $ip= "ip$suffix";
bedeaaf1 1393
6178b0dd
WB
1394 my $newip = $newnet->{$ip};
1395 my $newgw = $newnet->{$gw};
1396 my $oldip = $optdata->{$ip};
1397
1398 my $change_ip = &$safe_string_ne($oldip, $newip);
1399 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1400
84e0c123 1401 return if !$change_ip && !$change_gw;
68a05bb3 1402
84e0c123 1403 # step 1: add new IP, if this fails we cancel
6178b0dd 1404 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1405 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1406 if (my $err = $@) {
1407 warn $err;
1408 return;
1409 }
bedeaaf1 1410 }
bedeaaf1 1411
84e0c123
WB
1412 # step 2: replace gateway
1413 # If this fails we delete the added IP and cancel.
1414 # If it succeeds we save the config and delete the old IP, ignoring
1415 # errors. The config is then saved.
1416 # Note: 'ip route replace' can add
1417 if ($change_gw) {
6178b0dd 1418 if ($newgw) {
8d723477 1419 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1420 if (my $err = $@) {
1421 warn $err;
1422 # the route was not replaced, the old IP is still available
1423 # rollback (delete new IP) and cancel
1424 if ($change_ip) {
8d723477 1425 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1426 warn $@ if $@; # no need to die here
1427 }
1428 return;
1429 }
1430 } else {
8d723477 1431 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1432 # if the route was not deleted, the guest might have deleted it manually
1433 # warn and continue
1434 warn $@ if $@;
1435 }
2bfd1615 1436 }
2bfd1615 1437
6178b0dd 1438 # from this point on we save the configuration
84e0c123 1439 # step 3: delete old IP ignoring errors
6178b0dd 1440 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1441 # We need to enable promote_secondaries, otherwise our newly added
1442 # address will be removed along with the old one.
1443 my $promote = 0;
1444 eval {
1445 if ($ipversion == 4) {
1446 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1447 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1448 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1449 }
1450 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1451 };
84e0c123 1452 warn $@ if $@; # no need to die here
8d723477
WB
1453
1454 if ($ipversion == 4) {
1455 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1456 }
bedeaaf1
AD
1457 }
1458
84e0c123
WB
1459 foreach my $property ($ip, $gw) {
1460 if ($newnet->{$property}) {
1461 $optdata->{$property} = $newnet->{$property};
1462 } else {
1463 delete $optdata->{$property};
1464 }
bedeaaf1 1465 }
18862537 1466 $conf->{$opt} = print_lxc_network($optdata);
84e0c123
WB
1467 PVE::LXC::write_config($vmid, $conf);
1468 $lxc_setup->setup_network($conf);
1469 };
bedeaaf1 1470
f39002a6
DM
1471 &$change_ip_config(4);
1472 &$change_ip_config(6);
489e960d
WL
1473
1474}
1475
a92f66c9
WL
1476# Internal snapshots
1477
1478# NOTE: Snapshot create/delete involves several non-atomic
1479# action, and can take a long time.
1480# So we try to avoid locking the file and use 'lock' variable
1481# inside the config file instead.
1482
1483my $snapshot_copy_config = sub {
1484 my ($source, $dest) = @_;
1485
1486 foreach my $k (keys %$source) {
1487 next if $k eq 'snapshots';
09d3ec42
DM
1488 next if $k eq 'snapstate';
1489 next if $k eq 'snaptime';
1490 next if $k eq 'vmstate';
1491 next if $k eq 'lock';
a92f66c9 1492 next if $k eq 'digest';
09d3ec42 1493 next if $k eq 'description';
a92f66c9
WL
1494
1495 $dest->{$k} = $source->{$k};
1496 }
1497};
1498
1499my $snapshot_prepare = sub {
1500 my ($vmid, $snapname, $comment) = @_;
1501
1502 my $snap;
1503
1504 my $updatefn = sub {
1505
1506 my $conf = load_config($vmid);
1507
bb1ac2de
DM
1508 die "you can't take a snapshot if it's a template\n"
1509 if is_template($conf);
1510
a92f66c9
WL
1511 check_lock($conf);
1512
09d3ec42 1513 $conf->{lock} = 'snapshot';
a92f66c9
WL
1514
1515 die "snapshot name '$snapname' already used\n"
1516 if defined($conf->{snapshots}->{$snapname});
1517
1518 my $storecfg = PVE::Storage::config();
1519 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1520
1521 $snap = $conf->{snapshots}->{$snapname} = {};
1522
1523 &$snapshot_copy_config($conf, $snap);
1524
09d3ec42
DM
1525 $snap->{'snapstate'} = "prepare";
1526 $snap->{'snaptime'} = time();
1527 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1528 $conf->{snapshots}->{$snapname} = $snap;
1529
1530 PVE::LXC::write_config($vmid, $conf);
1531 };
1532
1533 lock_container($vmid, 10, $updatefn);
1534
1535 return $snap;
1536};
1537
1538my $snapshot_commit = sub {
1539 my ($vmid, $snapname) = @_;
1540
1541 my $updatefn = sub {
1542
1543 my $conf = load_config($vmid);
1544
1545 die "missing snapshot lock\n"
09d3ec42 1546 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1547
27916659 1548 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1549 if !defined($conf->{snapshots}->{$snapname});
1550
1551 die "wrong snapshot state\n"
09d3ec42
DM
1552 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1553 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1554
09d3ec42
DM
1555 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1556 delete $conf->{lock};
1557 $conf->{parent} = $snapname;
a92f66c9
WL
1558
1559 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1560 };
1561
1562 lock_container($vmid, 10 ,$updatefn);
1563};
1564
1565sub has_feature {
1566 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1567
a92f66c9
WL
1568 #Fixme add other drives if necessary.
1569 my $err;
09d3ec42
DM
1570
1571 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1572 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1573
1574 return $err ? 0 : 1;
1575}
1576
489e960d
WL
1577sub snapshot_create {
1578 my ($vmid, $snapname, $comment) = @_;
1579
a92f66c9
WL
1580 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1581
09d3ec42 1582 my $conf = load_config($vmid);
a92f66c9
WL
1583
1584 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1585 my $running = check_running($vmid);
1586 eval {
1587 if ($running) {
1588 PVE::Tools::run_command($cmd);
1589 };
1590
1591 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1592 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1593 my $volid = $rootinfo->{volume};
a92f66c9
WL
1594
1595 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1596 if ($running) {
1597 PVE::Tools::run_command($cmd);
1598 };
489e960d 1599
a92f66c9
WL
1600 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1601 &$snapshot_commit($vmid, $snapname);
1602 };
1603 if(my $err = $@) {
31429832 1604 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1605 die "$err\n";
1606 }
68a05bb3
AD
1607}
1608
57ccb3f8
WL
1609sub snapshot_delete {
1610 my ($vmid, $snapname, $force) = @_;
1611
31429832
WL
1612 my $snap;
1613
1614 my $conf;
1615
1616 my $updatefn = sub {
1617
1618 $conf = load_config($vmid);
1619
bb1ac2de
DM
1620 die "you can't delete a snapshot if vm is a template\n"
1621 if is_template($conf);
1622
31429832
WL
1623 $snap = $conf->{snapshots}->{$snapname};
1624
1625 check_lock($conf);
1626
1627 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1628
09d3ec42 1629 $snap->{snapstate} = 'delete';
31429832
WL
1630
1631 PVE::LXC::write_config($vmid, $conf);
1632 };
1633
1634 lock_container($vmid, 10, $updatefn);
1635
1636 my $storecfg = PVE::Storage::config();
1637
1638 my $del_snap = sub {
1639
1640 check_lock($conf);
1641
09d3ec42
DM
1642 if ($conf->{parent} eq $snapname) {
1643 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1644 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1645 } else {
09d3ec42 1646 delete $conf->{parent};
31429832
WL
1647 }
1648 }
1649
1650 delete $conf->{snapshots}->{$snapname};
1651
1652 PVE::LXC::write_config($vmid, $conf);
1653 };
1654
09d3ec42
DM
1655 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1656 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1657 my $volid = $rootinfo->{volume};
31429832
WL
1658
1659 eval {
1660 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1661 };
1662 my $err = $@;
1663
1664 if(!$err || ($err && $force)) {
1665 lock_container($vmid, 10, $del_snap);
1666 if ($err) {
1667 die "Can't delete snapshot: $vmid $snapname $err\n";
1668 }
1669 }
57ccb3f8
WL
1670}
1671
723157f6
WL
1672sub snapshot_rollback {
1673 my ($vmid, $snapname) = @_;
1674
6860ba0c
WL
1675 my $storecfg = PVE::Storage::config();
1676
1677 my $conf = load_config($vmid);
1678
bb1ac2de
DM
1679 die "you can't rollback if vm is a template\n" if is_template($conf);
1680
6860ba0c
WL
1681 my $snap = $conf->{snapshots}->{$snapname};
1682
1683 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1684
09d3ec42
DM
1685 my $rootfs = $snap->{rootfs};
1686 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1687 my $volid = $rootinfo->{volume};
1688
1689 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1690
1691 my $updatefn = sub {
1692
09d3ec42
DM
1693 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1694 if $snap->{snapstate};
6860ba0c
WL
1695
1696 check_lock($conf);
6860ba0c 1697
b935932a 1698 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1699
1700 die "unable to rollback vm $vmid: vm is running\n"
1701 if check_running($vmid);
1702
09d3ec42 1703 $conf->{lock} = 'rollback';
6860ba0c
WL
1704
1705 my $forcemachine;
1706
1707 # copy snapshot config to current config
1708
1709 my $tmp_conf = $conf;
1710 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1711 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1712 delete $conf->{snaptime};
1713 delete $conf->{snapname};
1714 $conf->{parent} = $snapname;
6860ba0c
WL
1715
1716 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1717 };
1718
1719 my $unlockfn = sub {
09d3ec42 1720 delete $conf->{lock};
6860ba0c
WL
1721 PVE::LXC::write_config($vmid, $conf);
1722 };
1723
1724 lock_container($vmid, 10, $updatefn);
1725
09d3ec42 1726 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1727
1728 lock_container($vmid, 5, $unlockfn);
723157f6 1729}
b935932a 1730
bb1ac2de
DM
1731sub template_create {
1732 my ($vmid, $conf) = @_;
1733
1734 my $storecfg = PVE::Storage::config();
1735
1736 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1737 my $volid = $rootinfo->{volume};
1738
1739 die "Template feature is not available for '$volid'\n"
1740 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1741
1742 PVE::Storage::activate_volumes($storecfg, [$volid]);
1743
1744 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1745 $rootinfo->{volume} = $template_volid;
dde7b02b 1746 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
bb1ac2de
DM
1747
1748 write_config($vmid, $conf);
1749}
1750
1751sub is_template {
1752 my ($conf) = @_;
1753
1754 return 1 if defined $conf->{template} && $conf->{template} == 1;
1755}
1756
ced7fddb
AD
1757sub foreach_mountpoint {
1758 my ($conf, $func) = @_;
1759
eaebef36
DM
1760 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
1761 $mountpoint->{mp} = '/'; # just to be sure
1762 &$func('rootfs', $mountpoint);
1763
1764 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1765 my $key = "mp$i";
1766 next if !defined($conf->{$key});
1767 $mountpoint = parse_ct_mountpoint($conf->{$key});
1768 &$func($key, $mountpoint);
ced7fddb
AD
1769 }
1770}
1771
6d89c14d
AD
1772sub loopdevices_list {
1773
1774 my $loopdev = {};
1775 my $parser = sub {
1776 my $line = shift;
1777 if ($line =~ m/^(\/dev\/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1778 $loopdev->{$1} = $2;
1779 }
1780 };
1781
1782 PVE::Tools::run_command(['losetup'], outfunc => $parser);
1783
1784 return $loopdev;
1785}
54304b66
AD
1786
1787sub blockdevices_list {
1788
1789 my $bdevs = {};
1790 dir_glob_foreach("/sys/dev/block/", '(\d+):(\d+)', sub {
1791 my (undef, $major, $minor) = @_;
1792 my $bdev = readlink("/sys/dev/block/$major:$minor");
1793 $bdev =~ s/\.\.\/\.\.\/devices\/virtual\/block\//\/dev\//;
1794 $bdevs->{$bdev}->{major} = $major;
1795 $bdevs->{$bdev}->{minor} = $minor;
1796 });
1797 return $bdevs;
1798}
1799
6b33ba58
AD
1800sub find_loopdev {
1801 my ($loopdevs, $path) = @_;
1802
1803 foreach my $dev (keys %$loopdevs){
1804 return $dev if $loopdevs->{$dev} eq $path;
1805 }
1806}
f5635601 1807
52389a07
DM
1808sub check_ct_modify_config_perm {
1809 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1810
1811 return 1 if $authuser ne 'root@pam';
1812
1813 foreach my $opt (@$key_list) {
1814
1815 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1816 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1817 } elsif ($opt eq 'disk') {
1818 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1819 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1820 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1821 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1822 $opt eq 'searchdomain' || $opt eq 'hostname') {
1823 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1824 } else {
1825 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1826 }
1827 }
1828
1829 return 1;
1830}
1831
bafcd6d4
AD
1832
1833sub volid_path {
a3076d81 1834 my ($volid, $storage_cfg, $loopdevs) = @_;
bafcd6d4 1835
6aac73b9 1836 my $path;
bafcd6d4 1837
6aac73b9 1838 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
bafcd6d4 1839
6aac73b9 1840 if ($storage) {
bafcd6d4 1841
6aac73b9
DM
1842 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1843 $path = PVE::Storage::path($storage_cfg, $volid);
bafcd6d4 1844
6aac73b9
DM
1845 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1846 PVE::Storage::parse_volname($storage_cfg, $volid);
d3f0f59c 1847
6aac73b9
DM
1848 die "unable to use template as mountpoint\n" if $isBase;
1849
1850 if ($format eq 'subvol') {
1851 # do nothing
1852 } elsif ($format eq 'raw') {
1853
1854 if ($scfg->{path}) {
1855 $path = PVE::LXC::find_loopdev($loopdevs, $path) if $loopdevs;
1856 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1857 # do nothing
d3f0f59c 1858 } else {
6aac73b9 1859 die "unsupported storage type '$scfg->{type}'\n";
d3f0f59c 1860 }
d3f0f59c 1861 } else {
6aac73b9 1862 die "unsupported image format '$format'\n";
d3f0f59c 1863 }
6aac73b9
DM
1864 } elsif ($volid =~ m|^/dev/.+|) {
1865 $path = $volid;
1866 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
1867 $path = $volid;
1868 } else {
1869 die "unsupported storage";
1870 }
bafcd6d4 1871
6aac73b9 1872 return $path;
bafcd6d4 1873}
571b33da
AD
1874
1875sub attach_loops {
1876 my ($storage_cfg, $vollist) = @_;
1877
1878 my $loopdevs = {};
1879
1880 foreach my $volid (@$vollist) {
1881
1882 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1883 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1884
1885 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1886 PVE::Storage::parse_volname($storage_cfg, $volid);
1887
35f92a09
DM
1888 if (($format ne 'subvol') &&
1889 ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
571b33da
AD
1890 my $path = PVE::Storage::path($storage_cfg, $volid);
1891 my $loopdev;
1892
1893 my $parser = sub {
1894 my $line = shift;
1895 $loopdev = $line if $line =~m|^/dev/loop\d+$|;
1896 $loopdevs->{$loopdev} = $path;
1897 };
1898
1899 PVE::Tools::run_command(['losetup', '--find', '--show', $path], outfunc => $parser);
1900 }
1901 }
35f92a09 1902
571b33da
AD
1903 return $loopdevs;
1904}
1905
a8b6b8a7
AD
1906sub dettach_loops {
1907 my ($storage_cfg, $vollist) = @_;
1908
1909 my $loopdevs = loopdevices_list();
1910
1911 foreach my $volid (@$vollist) {
1912
1913 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1914 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1915
1916 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1917 PVE::Storage::parse_volname($storage_cfg, $volid);
1918
1919 if($format ne 'subvol' && ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
1920 my $path = PVE::Storage::path($storage_cfg, $volid);
1921 foreach my $dev (keys %$loopdevs){
1922 PVE::Tools::run_command(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1923 }
1924 }
1925 }
1926}
1927
cc6b0307
AD
1928
1929sub mountpoint_mount {
1e8f01be 1930 my ($mountpoint, $rootdir, $storage_cfg, $loopdevs) = @_;
cc6b0307
AD
1931
1932 my $volid = $mountpoint->{volume};
1933 my $mount = $mountpoint->{mp};
1934
1935 return if !$volid || !$mount;
1936
116ce06f
DM
1937 $rootdir =~ s!/+$!!;
1938 my $mount_path = "$rootdir/$mount";
1939 File::Path::mkpath($mount_path);
cc6b0307 1940
116ce06f
DM
1941 if ($volid =~ m|^/dev/.+|) {
1942 PVE::Tools::run_command(['mount', $volid, $mount_path]);
1943 return;
1944 }
cc6b0307 1945
116ce06f 1946 my $path = PVE::LXC::volid_path($volid, $storage_cfg, $loopdevs);
cc6b0307 1947
116ce06f
DM
1948 if ($path !~ m|^/dev/.+|) {
1949 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
1950 return;
1951 }
cc6b0307 1952
116ce06f 1953 PVE::Tools::run_command(['mount', $path, $mount_path]);
cc6b0307
AD
1954}
1955
9205e9d0
AD
1956sub get_vm_volumes {
1957 my ($conf, $excludes) = @_;
1958
1959 my $vollist = [];
1960
1961 PVE::LXC::foreach_mountpoint($conf, sub {
1962 my ($ms, $mountpoint) = @_;
1963
1964 return if $excludes && $ms eq $excludes;
1965
1966 my $volid = $mountpoint->{volume};
1967
1968 return if !$volid || $volid =~ m|^/|;
1969
1970 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1971 return if !$sid;
1972
1973 push @$vollist, $volid;
1974 });
1975
1976 return $vollist;
1977}
1978
f76a2828 19791;