]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
mount hook : 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
6aca6740
AD
1002 PVE::LXC::foreach_mountpoint($conf, sub {
1003 my ($ms, $mountpoint) = @_;
c628ffa1 1004
d410f592 1005 return if $ms ne 'rootfs';
6aca6740 1006 my $volid = $mountpoint->{volume};
27163d2f 1007 return if !$volid || $volid =~ m|^/dev/.+|;
453cb062 1008
d410f592
AD
1009 my $path = volid_path ($volid, $ms, $storage_cfg);
1010 $raw .= "lxc.rootfs = $path\n";
6aca6740 1011 });
27916659
DM
1012
1013 my $netcount = 0;
1014 foreach my $k (keys %$conf) {
1015 next if $k !~ m/^net(\d+)$/;
1016 my $ind = $1;
a16d94c8 1017 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1018 $netcount++;
1019 $raw .= "lxc.network.type = veth\n";
18862537 1020 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1021 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1022 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1023 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1024 }
1025
e576f689
DM
1026 if (my $lxcconf = $conf->{lxc}) {
1027 foreach my $entry (@$lxcconf) {
1028 my ($k, $v) = @$entry;
1029 $netcount++ if $k eq 'lxc.network.type';
1030 $raw .= "$k = $v\n";
1031 }
1032 }
27916659 1033
e576f689
DM
1034 $raw .= "lxc.network.type = empty\n" if !$netcount;
1035
27916659
DM
1036 File::Path::mkpath("$dir/rootfs");
1037
1038 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1039}
1040
117636e5
DM
1041# verify and cleanup nameserver list (replace \0 with ' ')
1042sub verify_nameserver_list {
1043 my ($nameserver_list) = @_;
1044
1045 my @list = ();
1046 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1047 PVE::JSONSchema::pve_verify_ip($server);
1048 push @list, $server;
1049 }
1050
1051 return join(' ', @list);
1052}
1053
1054sub verify_searchdomain_list {
1055 my ($searchdomain_list) = @_;
1056
1057 my @list = ();
1058 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1059 # todo: should we add checks for valid dns domains?
1060 push @list, $server;
1061 }
1062
1063 return join(' ', @list);
1064}
1065
27916659 1066sub update_pct_config {
93285df8
DM
1067 my ($vmid, $conf, $running, $param, $delete) = @_;
1068
bf0b8c43
AD
1069 my @nohotplug;
1070
cbb03fea
DM
1071 my $rootdir;
1072 if ($running) {
bedeaaf1 1073 my $pid = find_lxc_pid($vmid);
cbb03fea 1074 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1075 }
1076
93285df8
DM
1077 if (defined($delete)) {
1078 foreach my $opt (@$delete) {
27916659 1079 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1080 die "unable to delete required option '$opt'\n";
1081 } elsif ($opt eq 'swap') {
27916659 1082 delete $conf->{$opt};
bf0b8c43 1083 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1084 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1085 delete $conf->{$opt};
4f958489 1086 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1087 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
27916659 1088 delete $conf->{$opt};
bf0b8c43
AD
1089 push @nohotplug, $opt;
1090 next if $running;
68fba17b 1091 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1092 delete $conf->{$opt};
68fba17b
AD
1093 next if !$running;
1094 my $netid = $1;
18862537 1095 PVE::Network::veth_delete("veth${vmid}i$netid");
93285df8
DM
1096 } else {
1097 die "implement me"
1098 }
bf0b8c43 1099 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8
DM
1100 }
1101 }
1102
be6383d7
WB
1103 # There's no separate swap size to configure, there's memory and "total"
1104 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1105 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1106 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1107 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1108
1109 $wanted_memory //= ($conf->{memory} || 512);
1110 $wanted_swap //= ($conf->{swap} || 0);
1111
1112 my $total = $wanted_memory + $wanted_swap;
1113 if ($running) {
1114 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1115 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1116 }
27916659
DM
1117 $conf->{memory} = $wanted_memory;
1118 $conf->{swap} = $wanted_swap;
1119
1120 PVE::LXC::write_config($vmid, $conf) if $running;
be6383d7
WB
1121 }
1122
93285df8
DM
1123 foreach my $opt (keys %$param) {
1124 my $value = $param->{$opt};
1125 if ($opt eq 'hostname') {
27916659 1126 $conf->{$opt} = $value;
a99b3509 1127 } elsif ($opt eq 'onboot') {
27916659 1128 $conf->{$opt} = $value ? 1 : 0;
a3249355 1129 } elsif ($opt eq 'startup') {
27916659 1130 $conf->{$opt} = $value;
40603eb3 1131 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
e576f689
DM
1132 $conf->{$opt} = $value;
1133 push @nohotplug, $opt;
1134 next if $running;
ffa1d001 1135 } elsif ($opt eq 'nameserver') {
117636e5 1136 my $list = verify_nameserver_list($value);
27916659 1137 $conf->{$opt} = $list;
bf0b8c43
AD
1138 push @nohotplug, $opt;
1139 next if $running;
ffa1d001 1140 } elsif ($opt eq 'searchdomain') {
117636e5 1141 my $list = verify_searchdomain_list($value);
27916659 1142 $conf->{$opt} = $list;
bf0b8c43
AD
1143 push @nohotplug, $opt;
1144 next if $running;
45573f7c 1145 } elsif ($opt eq 'cpulimit') {
27916659
DM
1146 $conf->{$opt} = $value;
1147 push @nohotplug, $opt; # fixme: hotplug
1148 next;
b80dd50a 1149 } elsif ($opt eq 'cpuunits') {
27916659 1150 $conf->{$opt} = $value;
bf0b8c43 1151 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1152 } elsif ($opt eq 'description') {
27916659 1153 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1154 } elsif ($opt =~ m/^net(\d+)$/) {
1155 my $netid = $1;
a16d94c8 1156 my $net = parse_lxc_network($value);
27916659
DM
1157 if (!$running) {
1158 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1159 } else {
bedeaaf1
AD
1160 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1161 }
93285df8 1162 } else {
a92f66c9 1163 die "implement me: $opt";
93285df8 1164 }
bf0b8c43 1165 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8 1166 }
bf0b8c43 1167
5cfa0567
DM
1168 if ($running && scalar(@nohotplug)) {
1169 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1170 }
93285df8 1171}
c325b32f 1172
6f035afe
DM
1173sub has_dev_console {
1174 my ($conf) = @_;
1175
1176 return !(defined($conf->{console}) && !$conf->{console});
1177}
1178
0d0ca400
DM
1179sub get_tty_count {
1180 my ($conf) = @_;
1181
1182 return $conf->{tty} // $confdesc->{tty}->{default};
1183}
1184
aca816ad
DM
1185sub get_cmode {
1186 my ($conf) = @_;
1187
1188 return $conf->{cmode} // $confdesc->{cmode}->{default};
1189}
1190
1191sub get_console_command {
1192 my ($vmid, $conf) = @_;
1193
1194 my $cmode = get_cmode($conf);
1195
1196 if ($cmode eq 'console') {
1197 return ['lxc-console', '-n', $vmid, '-t', 0];
1198 } elsif ($cmode eq 'tty') {
1199 return ['lxc-console', '-n', $vmid];
1200 } elsif ($cmode eq 'shell') {
1201 return ['lxc-attach', '--clear-env', '-n', $vmid];
1202 } else {
1203 die "internal error";
1204 }
1205}
1206
c325b32f
DM
1207sub get_primary_ips {
1208 my ($conf) = @_;
1209
1210 # return data from net0
cbb03fea 1211
27916659 1212 return undef if !defined($conf->{net0});
a16d94c8 1213 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1214
1215 my $ipv4 = $net->{ip};
db78a181
WB
1216 if ($ipv4) {
1217 if ($ipv4 =~ /^(dhcp|manual)$/) {
1218 $ipv4 = undef
1219 } else {
1220 $ipv4 =~ s!/\d+$!!;
1221 }
1222 }
65e5eaa3 1223 my $ipv6 = $net->{ip6};
db78a181
WB
1224 if ($ipv6) {
1225 if ($ipv6 =~ /^(dhcp|manual)$/) {
1226 $ipv6 = undef;
1227 } else {
1228 $ipv6 =~ s!/\d+$!!;
1229 }
1230 }
cbb03fea 1231
c325b32f
DM
1232 return ($ipv4, $ipv6);
1233}
148d1cb4 1234
ef241384 1235
27916659 1236sub destroy_lxc_container {
148d1cb4
DM
1237 my ($storage_cfg, $vmid, $conf) = @_;
1238
27916659
DM
1239 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1240 if (defined($rootinfo->{volume})) {
1241 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
1242 PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
148d1cb4 1243 }
27916659
DM
1244 rmdir "/var/lib/lxc/$vmid/rootfs";
1245 unlink "/var/lib/lxc/$vmid/config";
1246 rmdir "/var/lib/lxc/$vmid";
1247 destroy_config($vmid);
1248
1249 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1250 #PVE::Tools::run_command($cmd);
148d1cb4 1251}
68fba17b 1252
ef241384 1253sub vm_stop_cleanup {
5fa890f0 1254 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
ef241384
DM
1255
1256 eval {
1257 if (!$keepActive) {
bf9d912c
AD
1258
1259 my $loopdevs = loopdevices_list();
1260
5fa890f0
AD
1261 PVE::LXC::foreach_mountpoint($conf, sub {
1262 my ($ms, $mountpoint) = @_;
bf9d912c
AD
1263
1264 my $volid = $mountpoint->{volume};
1265 #detach loopdevices of non rootfs mountpoints
1266 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1267 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1268 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1269 PVE::Storage::parse_volname($storage_cfg, $volid);
1270
1271 if($ms ne 'rootfs' && $format eq 'raw' && ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
1272 my $path = PVE::Storage::path($storage_cfg, $volid);
1273 foreach my $dev (keys %$loopdevs){
1274 PVE::Tools::run_command(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1275 }
1276 }
1277
1278 PVE::Storage::deactivate_volumes($storage_cfg, [$volid]);
1279
5fa890f0 1280 });
ef241384
DM
1281 }
1282 };
1283 warn $@ if $@; # avoid errors - just warn
1284}
1285
93cdbbfb
AD
1286my $safe_num_ne = sub {
1287 my ($a, $b) = @_;
1288
1289 return 0 if !defined($a) && !defined($b);
1290 return 1 if !defined($a);
1291 return 1 if !defined($b);
1292
1293 return $a != $b;
1294};
1295
1296my $safe_string_ne = sub {
1297 my ($a, $b) = @_;
1298
1299 return 0 if !defined($a) && !defined($b);
1300 return 1 if !defined($a);
1301 return 1 if !defined($b);
1302
1303 return $a ne $b;
1304};
1305
1306sub update_net {
bedeaaf1 1307 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1308
18862537
WB
1309 if ($newnet->{type} ne 'veth') {
1310 # for when there are physical interfaces
1311 die "cannot update interface of type $newnet->{type}";
1312 }
1313
1314 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1315 my $eth = $newnet->{name};
1316
18862537
WB
1317 if (my $oldnetcfg = $conf->{$opt}) {
1318 my $oldnet = parse_lxc_network($oldnetcfg);
1319
1320 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1321 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1322
18862537 1323 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1324 delete $conf->{$opt};
1325 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1326
18862537 1327 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1328
18862537
WB
1329 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1330 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1331 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1332
18862537 1333 if ($oldnet->{bridge}) {
bedeaaf1 1334 PVE::Network::tap_unplug($veth);
18862537
WB
1335 foreach (qw(bridge tag firewall)) {
1336 delete $oldnet->{$_};
1337 }
1338 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1
AD
1339 PVE::LXC::write_config($vmid, $conf);
1340 }
93cdbbfb 1341
18862537
WB
1342 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1343 foreach (qw(bridge tag firewall)) {
1344 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1345 }
1346 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1 1347 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1348 }
1349 } else {
18862537 1350 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1351 }
1352
bedeaaf1 1353 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1354}
1355
1356sub hotplug_net {
18862537 1357 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1358
18862537 1359 my $veth = "veth${vmid}i${netid}";
cbb03fea 1360 my $vethpeer = $veth . "p";
93cdbbfb
AD
1361 my $eth = $newnet->{name};
1362
1363 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1364 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1365
cbb03fea 1366 # attach peer in container
93cdbbfb
AD
1367 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1368 PVE::Tools::run_command($cmd);
1369
cbb03fea 1370 # link up peer in container
93cdbbfb
AD
1371 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1372 PVE::Tools::run_command($cmd);
bedeaaf1 1373
18862537
WB
1374 my $done = { type => 'veth' };
1375 foreach (qw(bridge tag firewall hwaddr name)) {
1376 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1377 }
1378 $conf->{$opt} = print_lxc_network($done);
bedeaaf1
AD
1379
1380 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1381}
1382
68a05bb3 1383sub update_ipconfig {
bedeaaf1
AD
1384 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1385
1386 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1387
18862537 1388 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1389 my $deleted = [];
1390 my $added = [];
8d723477
WB
1391 my $nscmd = sub {
1392 my $cmdargs = shift;
1393 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1394 };
8d723477 1395 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1396
84e0c123 1397 my $change_ip_config = sub {
f39002a6
DM
1398 my ($ipversion) = @_;
1399
1400 my $family_opt = "-$ipversion";
1401 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1402 my $gw= "gw$suffix";
1403 my $ip= "ip$suffix";
bedeaaf1 1404
6178b0dd
WB
1405 my $newip = $newnet->{$ip};
1406 my $newgw = $newnet->{$gw};
1407 my $oldip = $optdata->{$ip};
1408
1409 my $change_ip = &$safe_string_ne($oldip, $newip);
1410 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1411
84e0c123 1412 return if !$change_ip && !$change_gw;
68a05bb3 1413
84e0c123 1414 # step 1: add new IP, if this fails we cancel
6178b0dd 1415 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1416 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1417 if (my $err = $@) {
1418 warn $err;
1419 return;
1420 }
bedeaaf1 1421 }
bedeaaf1 1422
84e0c123
WB
1423 # step 2: replace gateway
1424 # If this fails we delete the added IP and cancel.
1425 # If it succeeds we save the config and delete the old IP, ignoring
1426 # errors. The config is then saved.
1427 # Note: 'ip route replace' can add
1428 if ($change_gw) {
6178b0dd 1429 if ($newgw) {
8d723477 1430 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1431 if (my $err = $@) {
1432 warn $err;
1433 # the route was not replaced, the old IP is still available
1434 # rollback (delete new IP) and cancel
1435 if ($change_ip) {
8d723477 1436 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1437 warn $@ if $@; # no need to die here
1438 }
1439 return;
1440 }
1441 } else {
8d723477 1442 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1443 # if the route was not deleted, the guest might have deleted it manually
1444 # warn and continue
1445 warn $@ if $@;
1446 }
2bfd1615 1447 }
2bfd1615 1448
6178b0dd 1449 # from this point on we save the configuration
84e0c123 1450 # step 3: delete old IP ignoring errors
6178b0dd 1451 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1452 # We need to enable promote_secondaries, otherwise our newly added
1453 # address will be removed along with the old one.
1454 my $promote = 0;
1455 eval {
1456 if ($ipversion == 4) {
1457 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1458 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1459 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1460 }
1461 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1462 };
84e0c123 1463 warn $@ if $@; # no need to die here
8d723477
WB
1464
1465 if ($ipversion == 4) {
1466 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1467 }
bedeaaf1
AD
1468 }
1469
84e0c123
WB
1470 foreach my $property ($ip, $gw) {
1471 if ($newnet->{$property}) {
1472 $optdata->{$property} = $newnet->{$property};
1473 } else {
1474 delete $optdata->{$property};
1475 }
bedeaaf1 1476 }
18862537 1477 $conf->{$opt} = print_lxc_network($optdata);
84e0c123
WB
1478 PVE::LXC::write_config($vmid, $conf);
1479 $lxc_setup->setup_network($conf);
1480 };
bedeaaf1 1481
f39002a6
DM
1482 &$change_ip_config(4);
1483 &$change_ip_config(6);
489e960d
WL
1484
1485}
1486
a92f66c9
WL
1487# Internal snapshots
1488
1489# NOTE: Snapshot create/delete involves several non-atomic
1490# action, and can take a long time.
1491# So we try to avoid locking the file and use 'lock' variable
1492# inside the config file instead.
1493
1494my $snapshot_copy_config = sub {
1495 my ($source, $dest) = @_;
1496
1497 foreach my $k (keys %$source) {
1498 next if $k eq 'snapshots';
09d3ec42
DM
1499 next if $k eq 'snapstate';
1500 next if $k eq 'snaptime';
1501 next if $k eq 'vmstate';
1502 next if $k eq 'lock';
a92f66c9 1503 next if $k eq 'digest';
09d3ec42 1504 next if $k eq 'description';
a92f66c9
WL
1505
1506 $dest->{$k} = $source->{$k};
1507 }
1508};
1509
1510my $snapshot_prepare = sub {
1511 my ($vmid, $snapname, $comment) = @_;
1512
1513 my $snap;
1514
1515 my $updatefn = sub {
1516
1517 my $conf = load_config($vmid);
1518
bb1ac2de
DM
1519 die "you can't take a snapshot if it's a template\n"
1520 if is_template($conf);
1521
a92f66c9
WL
1522 check_lock($conf);
1523
09d3ec42 1524 $conf->{lock} = 'snapshot';
a92f66c9
WL
1525
1526 die "snapshot name '$snapname' already used\n"
1527 if defined($conf->{snapshots}->{$snapname});
1528
1529 my $storecfg = PVE::Storage::config();
1530 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1531
1532 $snap = $conf->{snapshots}->{$snapname} = {};
1533
1534 &$snapshot_copy_config($conf, $snap);
1535
09d3ec42
DM
1536 $snap->{'snapstate'} = "prepare";
1537 $snap->{'snaptime'} = time();
1538 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1539 $conf->{snapshots}->{$snapname} = $snap;
1540
1541 PVE::LXC::write_config($vmid, $conf);
1542 };
1543
1544 lock_container($vmid, 10, $updatefn);
1545
1546 return $snap;
1547};
1548
1549my $snapshot_commit = sub {
1550 my ($vmid, $snapname) = @_;
1551
1552 my $updatefn = sub {
1553
1554 my $conf = load_config($vmid);
1555
1556 die "missing snapshot lock\n"
09d3ec42 1557 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1558
27916659 1559 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1560 if !defined($conf->{snapshots}->{$snapname});
1561
1562 die "wrong snapshot state\n"
09d3ec42
DM
1563 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1564 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1565
09d3ec42
DM
1566 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1567 delete $conf->{lock};
1568 $conf->{parent} = $snapname;
a92f66c9
WL
1569
1570 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1571 };
1572
1573 lock_container($vmid, 10 ,$updatefn);
1574};
1575
1576sub has_feature {
1577 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1578
a92f66c9
WL
1579 #Fixme add other drives if necessary.
1580 my $err;
09d3ec42
DM
1581
1582 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1583 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1584
1585 return $err ? 0 : 1;
1586}
1587
489e960d
WL
1588sub snapshot_create {
1589 my ($vmid, $snapname, $comment) = @_;
1590
a92f66c9
WL
1591 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1592
09d3ec42 1593 my $conf = load_config($vmid);
a92f66c9
WL
1594
1595 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1596 my $running = check_running($vmid);
1597 eval {
1598 if ($running) {
1599 PVE::Tools::run_command($cmd);
1600 };
1601
1602 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1603 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1604 my $volid = $rootinfo->{volume};
a92f66c9
WL
1605
1606 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1607 if ($running) {
1608 PVE::Tools::run_command($cmd);
1609 };
489e960d 1610
a92f66c9
WL
1611 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1612 &$snapshot_commit($vmid, $snapname);
1613 };
1614 if(my $err = $@) {
31429832 1615 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1616 die "$err\n";
1617 }
68a05bb3
AD
1618}
1619
57ccb3f8
WL
1620sub snapshot_delete {
1621 my ($vmid, $snapname, $force) = @_;
1622
31429832
WL
1623 my $snap;
1624
1625 my $conf;
1626
1627 my $updatefn = sub {
1628
1629 $conf = load_config($vmid);
1630
bb1ac2de
DM
1631 die "you can't delete a snapshot if vm is a template\n"
1632 if is_template($conf);
1633
31429832
WL
1634 $snap = $conf->{snapshots}->{$snapname};
1635
1636 check_lock($conf);
1637
1638 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1639
09d3ec42 1640 $snap->{snapstate} = 'delete';
31429832
WL
1641
1642 PVE::LXC::write_config($vmid, $conf);
1643 };
1644
1645 lock_container($vmid, 10, $updatefn);
1646
1647 my $storecfg = PVE::Storage::config();
1648
1649 my $del_snap = sub {
1650
1651 check_lock($conf);
1652
09d3ec42
DM
1653 if ($conf->{parent} eq $snapname) {
1654 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1655 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1656 } else {
09d3ec42 1657 delete $conf->{parent};
31429832
WL
1658 }
1659 }
1660
1661 delete $conf->{snapshots}->{$snapname};
1662
1663 PVE::LXC::write_config($vmid, $conf);
1664 };
1665
09d3ec42
DM
1666 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1667 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1668 my $volid = $rootinfo->{volume};
31429832
WL
1669
1670 eval {
1671 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1672 };
1673 my $err = $@;
1674
1675 if(!$err || ($err && $force)) {
1676 lock_container($vmid, 10, $del_snap);
1677 if ($err) {
1678 die "Can't delete snapshot: $vmid $snapname $err\n";
1679 }
1680 }
57ccb3f8
WL
1681}
1682
723157f6
WL
1683sub snapshot_rollback {
1684 my ($vmid, $snapname) = @_;
1685
6860ba0c
WL
1686 my $storecfg = PVE::Storage::config();
1687
1688 my $conf = load_config($vmid);
1689
bb1ac2de
DM
1690 die "you can't rollback if vm is a template\n" if is_template($conf);
1691
6860ba0c
WL
1692 my $snap = $conf->{snapshots}->{$snapname};
1693
1694 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1695
09d3ec42
DM
1696 my $rootfs = $snap->{rootfs};
1697 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1698 my $volid = $rootinfo->{volume};
1699
1700 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1701
1702 my $updatefn = sub {
1703
09d3ec42
DM
1704 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1705 if $snap->{snapstate};
6860ba0c
WL
1706
1707 check_lock($conf);
6860ba0c 1708
b935932a 1709 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1710
1711 die "unable to rollback vm $vmid: vm is running\n"
1712 if check_running($vmid);
1713
09d3ec42 1714 $conf->{lock} = 'rollback';
6860ba0c
WL
1715
1716 my $forcemachine;
1717
1718 # copy snapshot config to current config
1719
1720 my $tmp_conf = $conf;
1721 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1722 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1723 delete $conf->{snaptime};
1724 delete $conf->{snapname};
1725 $conf->{parent} = $snapname;
6860ba0c
WL
1726
1727 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1728 };
1729
1730 my $unlockfn = sub {
09d3ec42 1731 delete $conf->{lock};
6860ba0c
WL
1732 PVE::LXC::write_config($vmid, $conf);
1733 };
1734
1735 lock_container($vmid, 10, $updatefn);
1736
09d3ec42 1737 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1738
1739 lock_container($vmid, 5, $unlockfn);
723157f6 1740}
b935932a 1741
bb1ac2de
DM
1742sub template_create {
1743 my ($vmid, $conf) = @_;
1744
1745 my $storecfg = PVE::Storage::config();
1746
1747 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1748 my $volid = $rootinfo->{volume};
1749
1750 die "Template feature is not available for '$volid'\n"
1751 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1752
1753 PVE::Storage::activate_volumes($storecfg, [$volid]);
1754
1755 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1756 $rootinfo->{volume} = $template_volid;
dde7b02b 1757 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
bb1ac2de
DM
1758
1759 write_config($vmid, $conf);
1760}
1761
1762sub is_template {
1763 my ($conf) = @_;
1764
1765 return 1 if defined $conf->{template} && $conf->{template} == 1;
1766}
1767
ced7fddb
AD
1768sub foreach_mountpoint {
1769 my ($conf, $func) = @_;
1770
eaebef36
DM
1771 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
1772 $mountpoint->{mp} = '/'; # just to be sure
1773 &$func('rootfs', $mountpoint);
1774
1775 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1776 my $key = "mp$i";
1777 next if !defined($conf->{$key});
1778 $mountpoint = parse_ct_mountpoint($conf->{$key});
1779 &$func($key, $mountpoint);
ced7fddb
AD
1780 }
1781}
1782
6d89c14d
AD
1783sub loopdevices_list {
1784
1785 my $loopdev = {};
1786 my $parser = sub {
1787 my $line = shift;
1788 if ($line =~ m/^(\/dev\/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1789 $loopdev->{$1} = $2;
1790 }
1791 };
1792
1793 PVE::Tools::run_command(['losetup'], outfunc => $parser);
1794
1795 return $loopdev;
1796}
54304b66
AD
1797
1798sub blockdevices_list {
1799
1800 my $bdevs = {};
1801 dir_glob_foreach("/sys/dev/block/", '(\d+):(\d+)', sub {
1802 my (undef, $major, $minor) = @_;
1803 my $bdev = readlink("/sys/dev/block/$major:$minor");
1804 $bdev =~ s/\.\.\/\.\.\/devices\/virtual\/block\//\/dev\//;
1805 $bdevs->{$bdev}->{major} = $major;
1806 $bdevs->{$bdev}->{minor} = $minor;
1807 });
1808 return $bdevs;
1809}
1810
6b33ba58
AD
1811sub find_loopdev {
1812 my ($loopdevs, $path) = @_;
1813
1814 foreach my $dev (keys %$loopdevs){
1815 return $dev if $loopdevs->{$dev} eq $path;
1816 }
1817}
f5635601 1818
52389a07
DM
1819sub check_ct_modify_config_perm {
1820 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1821
1822 return 1 if $authuser ne 'root@pam';
1823
1824 foreach my $opt (@$key_list) {
1825
1826 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1827 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1828 } elsif ($opt eq 'disk') {
1829 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1830 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1831 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1832 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1833 $opt eq 'searchdomain' || $opt eq 'hostname') {
1834 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1835 } else {
1836 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1837 }
1838 }
1839
1840 return 1;
1841}
1842
bafcd6d4
AD
1843
1844sub volid_path {
1845 my ($volid, $ms, $storage_cfg, $loopdevs) = @_;
1846
1847 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1848 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1849 my $path = PVE::Storage::path($storage_cfg, $volid);
1850
1851 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1852 PVE::Storage::parse_volname($storage_cfg, $volid);
1853
1854 die "unable to use template as mountpoint\n" if $isBase;
1855
1856 if ($format eq 'subvol') {
1857 #do nothing
1858 } elsif ($format eq 'raw') {
1859
1860 if ($scfg->{path}) {
1861 if ($ms eq 'rootfs') {
1862 $path = "loop:$path\n" if $ms eq 'rootfs';
1863 } elsif ($loopdevs) {
1864 $path = PVE::LXC::find_loopdev($loopdevs, $path) if $loopdevs;
1865 }
1866
1867 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1868 #do nothing
1869 } else {
1870 die "unsupported storage type '$scfg->{type}'\n";
1871 }
1872 } else {
1873 die "unsupported image format '$format'\n";
1874 }
1875
1876 return $path;
1877
1878}
f76a2828 18791;