]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
add mountpoint_mount sub
[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 1258
a8b6b8a7
AD
1259 my $vollist = [];
1260 my $loopdevlist = [];
bf9d912c 1261
a8b6b8a7 1262 PVE::LXC::foreach_mountpoint($conf, sub {
5fa890f0 1263 my ($ms, $mountpoint) = @_;
bf9d912c
AD
1264
1265 my $volid = $mountpoint->{volume};
a8b6b8a7
AD
1266 return if !$volid || $volid =~ m|^/dev/.+|;
1267 push @$vollist, $volid;
1268 push @$loopdevlist, $volid if $ms ne 'rootfs';
1269 });
bf9d912c 1270
a8b6b8a7
AD
1271 PVE::LXC::dettach_loops($storage_cfg, $loopdevlist);
1272 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1273 }
1274 };
1275 warn $@ if $@; # avoid errors - just warn
1276}
1277
93cdbbfb
AD
1278my $safe_num_ne = sub {
1279 my ($a, $b) = @_;
1280
1281 return 0 if !defined($a) && !defined($b);
1282 return 1 if !defined($a);
1283 return 1 if !defined($b);
1284
1285 return $a != $b;
1286};
1287
1288my $safe_string_ne = sub {
1289 my ($a, $b) = @_;
1290
1291 return 0 if !defined($a) && !defined($b);
1292 return 1 if !defined($a);
1293 return 1 if !defined($b);
1294
1295 return $a ne $b;
1296};
1297
1298sub update_net {
bedeaaf1 1299 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1300
18862537
WB
1301 if ($newnet->{type} ne 'veth') {
1302 # for when there are physical interfaces
1303 die "cannot update interface of type $newnet->{type}";
1304 }
1305
1306 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1307 my $eth = $newnet->{name};
1308
18862537
WB
1309 if (my $oldnetcfg = $conf->{$opt}) {
1310 my $oldnet = parse_lxc_network($oldnetcfg);
1311
1312 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1313 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1314
18862537 1315 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1316 delete $conf->{$opt};
1317 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1318
18862537 1319 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1320
18862537
WB
1321 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1322 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1323 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1324
18862537 1325 if ($oldnet->{bridge}) {
bedeaaf1 1326 PVE::Network::tap_unplug($veth);
18862537
WB
1327 foreach (qw(bridge tag firewall)) {
1328 delete $oldnet->{$_};
1329 }
1330 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1
AD
1331 PVE::LXC::write_config($vmid, $conf);
1332 }
93cdbbfb 1333
18862537
WB
1334 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1335 foreach (qw(bridge tag firewall)) {
1336 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1337 }
1338 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1 1339 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1340 }
1341 } else {
18862537 1342 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1343 }
1344
bedeaaf1 1345 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1346}
1347
1348sub hotplug_net {
18862537 1349 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1350
18862537 1351 my $veth = "veth${vmid}i${netid}";
cbb03fea 1352 my $vethpeer = $veth . "p";
93cdbbfb
AD
1353 my $eth = $newnet->{name};
1354
1355 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1356 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1357
cbb03fea 1358 # attach peer in container
93cdbbfb
AD
1359 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1360 PVE::Tools::run_command($cmd);
1361
cbb03fea 1362 # link up peer in container
93cdbbfb
AD
1363 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1364 PVE::Tools::run_command($cmd);
bedeaaf1 1365
18862537
WB
1366 my $done = { type => 'veth' };
1367 foreach (qw(bridge tag firewall hwaddr name)) {
1368 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1369 }
1370 $conf->{$opt} = print_lxc_network($done);
bedeaaf1
AD
1371
1372 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1373}
1374
68a05bb3 1375sub update_ipconfig {
bedeaaf1
AD
1376 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1377
1378 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1379
18862537 1380 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1381 my $deleted = [];
1382 my $added = [];
8d723477
WB
1383 my $nscmd = sub {
1384 my $cmdargs = shift;
1385 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1386 };
8d723477 1387 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1388
84e0c123 1389 my $change_ip_config = sub {
f39002a6
DM
1390 my ($ipversion) = @_;
1391
1392 my $family_opt = "-$ipversion";
1393 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1394 my $gw= "gw$suffix";
1395 my $ip= "ip$suffix";
bedeaaf1 1396
6178b0dd
WB
1397 my $newip = $newnet->{$ip};
1398 my $newgw = $newnet->{$gw};
1399 my $oldip = $optdata->{$ip};
1400
1401 my $change_ip = &$safe_string_ne($oldip, $newip);
1402 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1403
84e0c123 1404 return if !$change_ip && !$change_gw;
68a05bb3 1405
84e0c123 1406 # step 1: add new IP, if this fails we cancel
6178b0dd 1407 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1408 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1409 if (my $err = $@) {
1410 warn $err;
1411 return;
1412 }
bedeaaf1 1413 }
bedeaaf1 1414
84e0c123
WB
1415 # step 2: replace gateway
1416 # If this fails we delete the added IP and cancel.
1417 # If it succeeds we save the config and delete the old IP, ignoring
1418 # errors. The config is then saved.
1419 # Note: 'ip route replace' can add
1420 if ($change_gw) {
6178b0dd 1421 if ($newgw) {
8d723477 1422 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1423 if (my $err = $@) {
1424 warn $err;
1425 # the route was not replaced, the old IP is still available
1426 # rollback (delete new IP) and cancel
1427 if ($change_ip) {
8d723477 1428 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1429 warn $@ if $@; # no need to die here
1430 }
1431 return;
1432 }
1433 } else {
8d723477 1434 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1435 # if the route was not deleted, the guest might have deleted it manually
1436 # warn and continue
1437 warn $@ if $@;
1438 }
2bfd1615 1439 }
2bfd1615 1440
6178b0dd 1441 # from this point on we save the configuration
84e0c123 1442 # step 3: delete old IP ignoring errors
6178b0dd 1443 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1444 # We need to enable promote_secondaries, otherwise our newly added
1445 # address will be removed along with the old one.
1446 my $promote = 0;
1447 eval {
1448 if ($ipversion == 4) {
1449 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1450 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1451 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1452 }
1453 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1454 };
84e0c123 1455 warn $@ if $@; # no need to die here
8d723477
WB
1456
1457 if ($ipversion == 4) {
1458 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1459 }
bedeaaf1
AD
1460 }
1461
84e0c123
WB
1462 foreach my $property ($ip, $gw) {
1463 if ($newnet->{$property}) {
1464 $optdata->{$property} = $newnet->{$property};
1465 } else {
1466 delete $optdata->{$property};
1467 }
bedeaaf1 1468 }
18862537 1469 $conf->{$opt} = print_lxc_network($optdata);
84e0c123
WB
1470 PVE::LXC::write_config($vmid, $conf);
1471 $lxc_setup->setup_network($conf);
1472 };
bedeaaf1 1473
f39002a6
DM
1474 &$change_ip_config(4);
1475 &$change_ip_config(6);
489e960d
WL
1476
1477}
1478
a92f66c9
WL
1479# Internal snapshots
1480
1481# NOTE: Snapshot create/delete involves several non-atomic
1482# action, and can take a long time.
1483# So we try to avoid locking the file and use 'lock' variable
1484# inside the config file instead.
1485
1486my $snapshot_copy_config = sub {
1487 my ($source, $dest) = @_;
1488
1489 foreach my $k (keys %$source) {
1490 next if $k eq 'snapshots';
09d3ec42
DM
1491 next if $k eq 'snapstate';
1492 next if $k eq 'snaptime';
1493 next if $k eq 'vmstate';
1494 next if $k eq 'lock';
a92f66c9 1495 next if $k eq 'digest';
09d3ec42 1496 next if $k eq 'description';
a92f66c9
WL
1497
1498 $dest->{$k} = $source->{$k};
1499 }
1500};
1501
1502my $snapshot_prepare = sub {
1503 my ($vmid, $snapname, $comment) = @_;
1504
1505 my $snap;
1506
1507 my $updatefn = sub {
1508
1509 my $conf = load_config($vmid);
1510
bb1ac2de
DM
1511 die "you can't take a snapshot if it's a template\n"
1512 if is_template($conf);
1513
a92f66c9
WL
1514 check_lock($conf);
1515
09d3ec42 1516 $conf->{lock} = 'snapshot';
a92f66c9
WL
1517
1518 die "snapshot name '$snapname' already used\n"
1519 if defined($conf->{snapshots}->{$snapname});
1520
1521 my $storecfg = PVE::Storage::config();
1522 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1523
1524 $snap = $conf->{snapshots}->{$snapname} = {};
1525
1526 &$snapshot_copy_config($conf, $snap);
1527
09d3ec42
DM
1528 $snap->{'snapstate'} = "prepare";
1529 $snap->{'snaptime'} = time();
1530 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1531 $conf->{snapshots}->{$snapname} = $snap;
1532
1533 PVE::LXC::write_config($vmid, $conf);
1534 };
1535
1536 lock_container($vmid, 10, $updatefn);
1537
1538 return $snap;
1539};
1540
1541my $snapshot_commit = sub {
1542 my ($vmid, $snapname) = @_;
1543
1544 my $updatefn = sub {
1545
1546 my $conf = load_config($vmid);
1547
1548 die "missing snapshot lock\n"
09d3ec42 1549 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1550
27916659 1551 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1552 if !defined($conf->{snapshots}->{$snapname});
1553
1554 die "wrong snapshot state\n"
09d3ec42
DM
1555 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1556 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1557
09d3ec42
DM
1558 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1559 delete $conf->{lock};
1560 $conf->{parent} = $snapname;
a92f66c9
WL
1561
1562 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1563 };
1564
1565 lock_container($vmid, 10 ,$updatefn);
1566};
1567
1568sub has_feature {
1569 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1570
a92f66c9
WL
1571 #Fixme add other drives if necessary.
1572 my $err;
09d3ec42
DM
1573
1574 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1575 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1576
1577 return $err ? 0 : 1;
1578}
1579
489e960d
WL
1580sub snapshot_create {
1581 my ($vmid, $snapname, $comment) = @_;
1582
a92f66c9
WL
1583 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1584
09d3ec42 1585 my $conf = load_config($vmid);
a92f66c9
WL
1586
1587 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1588 my $running = check_running($vmid);
1589 eval {
1590 if ($running) {
1591 PVE::Tools::run_command($cmd);
1592 };
1593
1594 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1595 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1596 my $volid = $rootinfo->{volume};
a92f66c9
WL
1597
1598 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1599 if ($running) {
1600 PVE::Tools::run_command($cmd);
1601 };
489e960d 1602
a92f66c9
WL
1603 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1604 &$snapshot_commit($vmid, $snapname);
1605 };
1606 if(my $err = $@) {
31429832 1607 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1608 die "$err\n";
1609 }
68a05bb3
AD
1610}
1611
57ccb3f8
WL
1612sub snapshot_delete {
1613 my ($vmid, $snapname, $force) = @_;
1614
31429832
WL
1615 my $snap;
1616
1617 my $conf;
1618
1619 my $updatefn = sub {
1620
1621 $conf = load_config($vmid);
1622
bb1ac2de
DM
1623 die "you can't delete a snapshot if vm is a template\n"
1624 if is_template($conf);
1625
31429832
WL
1626 $snap = $conf->{snapshots}->{$snapname};
1627
1628 check_lock($conf);
1629
1630 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1631
09d3ec42 1632 $snap->{snapstate} = 'delete';
31429832
WL
1633
1634 PVE::LXC::write_config($vmid, $conf);
1635 };
1636
1637 lock_container($vmid, 10, $updatefn);
1638
1639 my $storecfg = PVE::Storage::config();
1640
1641 my $del_snap = sub {
1642
1643 check_lock($conf);
1644
09d3ec42
DM
1645 if ($conf->{parent} eq $snapname) {
1646 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1647 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1648 } else {
09d3ec42 1649 delete $conf->{parent};
31429832
WL
1650 }
1651 }
1652
1653 delete $conf->{snapshots}->{$snapname};
1654
1655 PVE::LXC::write_config($vmid, $conf);
1656 };
1657
09d3ec42
DM
1658 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1659 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1660 my $volid = $rootinfo->{volume};
31429832
WL
1661
1662 eval {
1663 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1664 };
1665 my $err = $@;
1666
1667 if(!$err || ($err && $force)) {
1668 lock_container($vmid, 10, $del_snap);
1669 if ($err) {
1670 die "Can't delete snapshot: $vmid $snapname $err\n";
1671 }
1672 }
57ccb3f8
WL
1673}
1674
723157f6
WL
1675sub snapshot_rollback {
1676 my ($vmid, $snapname) = @_;
1677
6860ba0c
WL
1678 my $storecfg = PVE::Storage::config();
1679
1680 my $conf = load_config($vmid);
1681
bb1ac2de
DM
1682 die "you can't rollback if vm is a template\n" if is_template($conf);
1683
6860ba0c
WL
1684 my $snap = $conf->{snapshots}->{$snapname};
1685
1686 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1687
09d3ec42
DM
1688 my $rootfs = $snap->{rootfs};
1689 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1690 my $volid = $rootinfo->{volume};
1691
1692 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1693
1694 my $updatefn = sub {
1695
09d3ec42
DM
1696 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1697 if $snap->{snapstate};
6860ba0c
WL
1698
1699 check_lock($conf);
6860ba0c 1700
b935932a 1701 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1702
1703 die "unable to rollback vm $vmid: vm is running\n"
1704 if check_running($vmid);
1705
09d3ec42 1706 $conf->{lock} = 'rollback';
6860ba0c
WL
1707
1708 my $forcemachine;
1709
1710 # copy snapshot config to current config
1711
1712 my $tmp_conf = $conf;
1713 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1714 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1715 delete $conf->{snaptime};
1716 delete $conf->{snapname};
1717 $conf->{parent} = $snapname;
6860ba0c
WL
1718
1719 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1720 };
1721
1722 my $unlockfn = sub {
09d3ec42 1723 delete $conf->{lock};
6860ba0c
WL
1724 PVE::LXC::write_config($vmid, $conf);
1725 };
1726
1727 lock_container($vmid, 10, $updatefn);
1728
09d3ec42 1729 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1730
1731 lock_container($vmid, 5, $unlockfn);
723157f6 1732}
b935932a 1733
bb1ac2de
DM
1734sub template_create {
1735 my ($vmid, $conf) = @_;
1736
1737 my $storecfg = PVE::Storage::config();
1738
1739 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1740 my $volid = $rootinfo->{volume};
1741
1742 die "Template feature is not available for '$volid'\n"
1743 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1744
1745 PVE::Storage::activate_volumes($storecfg, [$volid]);
1746
1747 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1748 $rootinfo->{volume} = $template_volid;
dde7b02b 1749 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
bb1ac2de
DM
1750
1751 write_config($vmid, $conf);
1752}
1753
1754sub is_template {
1755 my ($conf) = @_;
1756
1757 return 1 if defined $conf->{template} && $conf->{template} == 1;
1758}
1759
ced7fddb
AD
1760sub foreach_mountpoint {
1761 my ($conf, $func) = @_;
1762
eaebef36
DM
1763 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
1764 $mountpoint->{mp} = '/'; # just to be sure
1765 &$func('rootfs', $mountpoint);
1766
1767 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1768 my $key = "mp$i";
1769 next if !defined($conf->{$key});
1770 $mountpoint = parse_ct_mountpoint($conf->{$key});
1771 &$func($key, $mountpoint);
ced7fddb
AD
1772 }
1773}
1774
6d89c14d
AD
1775sub loopdevices_list {
1776
1777 my $loopdev = {};
1778 my $parser = sub {
1779 my $line = shift;
1780 if ($line =~ m/^(\/dev\/loop\d+)\s+\d\s+\d\s+\d\s+\d\s(\S+)$/) {
1781 $loopdev->{$1} = $2;
1782 }
1783 };
1784
1785 PVE::Tools::run_command(['losetup'], outfunc => $parser);
1786
1787 return $loopdev;
1788}
54304b66
AD
1789
1790sub blockdevices_list {
1791
1792 my $bdevs = {};
1793 dir_glob_foreach("/sys/dev/block/", '(\d+):(\d+)', sub {
1794 my (undef, $major, $minor) = @_;
1795 my $bdev = readlink("/sys/dev/block/$major:$minor");
1796 $bdev =~ s/\.\.\/\.\.\/devices\/virtual\/block\//\/dev\//;
1797 $bdevs->{$bdev}->{major} = $major;
1798 $bdevs->{$bdev}->{minor} = $minor;
1799 });
1800 return $bdevs;
1801}
1802
6b33ba58
AD
1803sub find_loopdev {
1804 my ($loopdevs, $path) = @_;
1805
1806 foreach my $dev (keys %$loopdevs){
1807 return $dev if $loopdevs->{$dev} eq $path;
1808 }
1809}
f5635601 1810
52389a07
DM
1811sub check_ct_modify_config_perm {
1812 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1813
1814 return 1 if $authuser ne 'root@pam';
1815
1816 foreach my $opt (@$key_list) {
1817
1818 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1819 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1820 } elsif ($opt eq 'disk') {
1821 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1822 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1823 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1824 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1825 $opt eq 'searchdomain' || $opt eq 'hostname') {
1826 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1827 } else {
1828 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1829 }
1830 }
1831
1832 return 1;
1833}
1834
bafcd6d4
AD
1835
1836sub volid_path {
1837 my ($volid, $ms, $storage_cfg, $loopdevs) = @_;
1838
1839 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1840 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1841 my $path = PVE::Storage::path($storage_cfg, $volid);
1842
1843 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1844 PVE::Storage::parse_volname($storage_cfg, $volid);
1845
1846 die "unable to use template as mountpoint\n" if $isBase;
1847
1848 if ($format eq 'subvol') {
1849 #do nothing
1850 } elsif ($format eq 'raw') {
1851
1852 if ($scfg->{path}) {
1853 if ($ms eq 'rootfs') {
1854 $path = "loop:$path\n" if $ms eq 'rootfs';
1855 } elsif ($loopdevs) {
1856 $path = PVE::LXC::find_loopdev($loopdevs, $path) if $loopdevs;
1857 }
1858
1859 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1860 #do nothing
1861 } else {
1862 die "unsupported storage type '$scfg->{type}'\n";
1863 }
1864 } else {
1865 die "unsupported image format '$format'\n";
1866 }
1867
1868 return $path;
1869
1870}
571b33da
AD
1871
1872sub attach_loops {
1873 my ($storage_cfg, $vollist) = @_;
1874
1875 my $loopdevs = {};
1876
1877 foreach my $volid (@$vollist) {
1878
1879 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1880 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1881
1882 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1883 PVE::Storage::parse_volname($storage_cfg, $volid);
1884
1885 if($format ne 'subvol' && ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
1886 my $path = PVE::Storage::path($storage_cfg, $volid);
1887 my $loopdev;
1888
1889 my $parser = sub {
1890 my $line = shift;
1891 $loopdev = $line if $line =~m|^/dev/loop\d+$|;
1892 $loopdevs->{$loopdev} = $path;
1893 };
1894
1895 PVE::Tools::run_command(['losetup', '--find', '--show', $path], outfunc => $parser);
1896 }
1897 }
1898 return $loopdevs;
1899}
1900
a8b6b8a7
AD
1901sub dettach_loops {
1902 my ($storage_cfg, $vollist) = @_;
1903
1904 my $loopdevs = loopdevices_list();
1905
1906 foreach my $volid (@$vollist) {
1907
1908 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
1909 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1910
1911 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1912 PVE::Storage::parse_volname($storage_cfg, $volid);
1913
1914 if($format ne 'subvol' && ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs')) {
1915 my $path = PVE::Storage::path($storage_cfg, $volid);
1916 foreach my $dev (keys %$loopdevs){
1917 PVE::Tools::run_command(['losetup', '-d', $dev]) if $loopdevs->{$dev} eq $path;
1918 }
1919 }
1920 }
1921}
1922
cc6b0307
AD
1923
1924sub mountpoint_mount {
1925 my ($ms, $mountpoint, $rootdir, $storage_cfg, $loopdevs) = @_;
1926
1927 my $volid = $mountpoint->{volume};
1928 my $mount = $mountpoint->{mp};
1929
1930 return if !$volid || !$mount;
1931
1932 eval {
1933 my $mount_path = $rootdir.$mount;
1934 File::Path::mkpath($mount_path);
1935
1936 if ($volid =~ m|^/dev/.+|) {
1937 PVE::Tools::run_command(['mount', $volid, $mount_path]);
1938 return;
1939 }
1940
1941 my $path = PVE::LXC::volid_path($volid, $ms, $storage_cfg, $loopdevs);
1942
1943 if ($path !~ m|^/dev/.+|) {
1944 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
1945 return;
1946 }
1947
1948 PVE::Tools::run_command(['mount', $path, $mount_path]);
1949 };
1950 warn $@ if $@;
1951}
1952
f76a2828 19531;