12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
21 use Time
::HiRes qw
(gettimeofday
);
25 my $nodename = PVE
::INotify
::nodename
();
27 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
29 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
31 '--xattrs-include=user.*',
32 '--xattrs-include=security.capability',
33 '--warning=no-xattr-write' ];
35 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
41 format_description
=> 'volume',
42 description
=> 'Volume, device or directory to mount into the container.',
46 format_description
=> '[1|0]',
47 description
=> 'Whether to include the mountpoint in backups.',
52 format
=> 'disk-size',
53 format_description
=> 'DiskSize',
54 description
=> 'Volume size (read only value).',
59 format_description
=> 'acl',
60 description
=> 'Explicitly enable or disable ACL support.',
65 format_description
=> 'ro',
66 description
=> 'Read-only mountpoint (not supported with bind mounts)',
71 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
72 type
=> 'string', format
=> $rootfs_desc,
73 description
=> "Use volume as container root.",
77 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
78 description
=> "The name of the snapshot.",
79 type
=> 'string', format
=> 'pve-configid',
87 description
=> "Lock/unlock the VM.",
88 enum
=> [qw(migrate backup snapshot rollback)],
93 description
=> "Specifies whether a VM will be started during system bootup.",
96 startup
=> get_standard_option
('pve-startup-order'),
100 description
=> "Enable/disable Template.",
106 enum
=> ['amd64', 'i386'],
107 description
=> "OS architecture type.",
113 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'],
114 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
119 description
=> "Attach a console device (/dev/console) to the container.",
125 description
=> "Specify the number of tty available to the container",
133 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
141 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 the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
149 description
=> "Amount of RAM for the VM in MB.",
156 description
=> "Amount of SWAP for the VM in MB.",
162 description
=> "Set a host name for the container.",
163 type
=> 'string', format
=> 'dns-name',
169 description
=> "Container description. Only used on the configuration web interface.",
173 type
=> 'string', format
=> 'dns-name-list',
174 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
178 type
=> 'string', format
=> 'address-list',
179 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
181 rootfs
=> get_standard_option
('pve-ct-rootfs'),
184 type
=> 'string', format
=> 'pve-configid',
186 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
190 description
=> "Timestamp for snapshots.",
196 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).",
198 enum
=> ['shell', 'console', 'tty'],
204 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
210 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
215 my $valid_lxc_conf_keys = {
219 'lxc.haltsignal' => 1,
220 'lxc.rebootsignal' => 1,
221 'lxc.stopsignal' => 1,
223 'lxc.network.type' => 1,
224 'lxc.network.flags' => 1,
225 'lxc.network.link' => 1,
226 'lxc.network.mtu' => 1,
227 'lxc.network.name' => 1,
228 'lxc.network.hwaddr' => 1,
229 'lxc.network.ipv4' => 1,
230 'lxc.network.ipv4.gateway' => 1,
231 'lxc.network.ipv6' => 1,
232 'lxc.network.ipv6.gateway' => 1,
233 'lxc.network.script.up' => 1,
234 'lxc.network.script.down' => 1,
236 'lxc.console.logfile' => 1,
239 'lxc.devttydir' => 1,
240 'lxc.hook.autodev' => 1,
244 'lxc.mount.entry' => 1,
245 'lxc.mount.auto' => 1,
246 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
247 'lxc.rootfs.mount' => 1,
248 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
249 ', please use mountpoint options in the "rootfs" key',
253 'lxc.aa_profile' => 1,
254 'lxc.aa_allow_incomplete' => 1,
255 'lxc.se_context' => 1,
258 'lxc.hook.pre-start' => 1,
259 'lxc.hook.pre-mount' => 1,
260 'lxc.hook.mount' => 1,
261 'lxc.hook.start' => 1,
262 'lxc.hook.stop' => 1,
263 'lxc.hook.post-stop' => 1,
264 'lxc.hook.clone' => 1,
265 'lxc.hook.destroy' => 1,
268 'lxc.start.auto' => 1,
269 'lxc.start.delay' => 1,
270 'lxc.start.order' => 1,
272 'lxc.environment' => 1,
279 description
=> "Network interface type.",
284 format_description
=> 'String',
285 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
286 pattern
=> '[-_.\w\d]+',
290 format_description
=> 'vmbr<Number>',
291 description
=> 'Bridge to attach the network device to.',
292 pattern
=> '[-_.\w\d]+',
297 format_description
=> 'MAC',
298 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
299 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
304 format_description
=> 'Number',
305 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
306 minimum
=> 64, # minimum ethernet frame is 64 bytes
311 format
=> 'pve-ipv4-config',
312 format_description
=> 'IPv4Format/CIDR',
313 description
=> 'IPv4 address in CIDR format.',
319 format_description
=> 'GatewayIPv4',
320 description
=> 'Default gateway for IPv4 traffic.',
325 format
=> 'pve-ipv6-config',
326 format_description
=> 'IPv6Format/CIDR',
327 description
=> 'IPv6 address in CIDR format.',
333 format_description
=> 'GatewayIPv6',
334 description
=> 'Default gateway for IPv6 traffic.',
339 format_description
=> '[1|0]',
340 description
=> "Controls whether this interface's firewall rules should be used.",
345 format_description
=> 'VlanNo',
348 description
=> "VLAN tag for this interface.",
353 pattern
=> qr/\d+(?:;\d+)*/,
354 format_description
=> 'vlanid[;vlanid...]',
355 description
=> "VLAN ids to pass through the interface",
359 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
361 my $MAX_LXC_NETWORKS = 10;
362 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
363 $confdesc->{"net$i"} = {
365 type
=> 'string', format
=> $netconf_desc,
366 description
=> "Specifies network interfaces for the container.",
374 format_description
=> 'Path',
375 description
=> 'Path to the mountpoint as seen from inside the container.',
378 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
382 type
=> 'string', format
=> 'pve-volume-id',
383 description
=> "Reference to unused volumes.",
386 my $MAX_MOUNT_POINTS = 10;
387 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
388 $confdesc->{"mp$i"} = {
390 type
=> 'string', format
=> $mp_desc,
391 description
=> "Use volume as container mount point (experimental feature).",
396 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
397 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
398 $confdesc->{"unused$i"} = $unuseddesc;
401 sub write_pct_config
{
402 my ($filename, $conf) = @_;
404 delete $conf->{snapstate
}; # just to be sure
406 my $generate_raw_config = sub {
411 # add description as comment to top of file
412 my $descr = $conf->{description
} || '';
413 foreach my $cl (split(/\n/, $descr)) {
414 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
417 foreach my $key (sort keys %$conf) {
418 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
419 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
420 my $value = $conf->{$key};
421 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
422 $raw .= "$key: $value\n";
425 if (my $lxcconf = $conf->{lxc
}) {
426 foreach my $entry (@$lxcconf) {
427 my ($k, $v) = @$entry;
435 my $raw = &$generate_raw_config($conf);
437 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
438 $raw .= "\n[$snapname]\n";
439 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
446 my ($key, $value) = @_;
448 die "unknown setting '$key'\n" if !$confdesc->{$key};
450 my $type = $confdesc->{$key}->{type
};
452 if (!defined($value)) {
453 die "got undefined value\n";
456 if ($value =~ m/[\n\r]/) {
457 die "property contains a line feed\n";
460 if ($type eq 'boolean') {
461 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
462 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
463 die "type check ('boolean') failed - got '$value'\n";
464 } elsif ($type eq 'integer') {
465 return int($1) if $value =~ m/^(\d+)$/;
466 die "type check ('integer') failed - got '$value'\n";
467 } elsif ($type eq 'number') {
468 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
469 die "type check ('number') failed - got '$value'\n";
470 } elsif ($type eq 'string') {
471 if (my $fmt = $confdesc->{$key}->{format
}) {
472 PVE
::JSONSchema
::check_format
($fmt, $value);
481 sub parse_pct_config
{
482 my ($filename, $raw) = @_;
484 return undef if !defined($raw);
487 digest
=> Digest
::SHA
::sha1_hex
($raw),
491 $filename =~ m
|/lxc/(\d
+).conf
$|
492 || die "got strange filename '$filename'";
500 my @lines = split(/\n/, $raw);
501 foreach my $line (@lines) {
502 next if $line =~ m/^\s*$/;
504 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
506 $conf->{description
} = $descr if $descr;
508 $conf = $res->{snapshots
}->{$section} = {};
512 if ($line =~ m/^\#(.*)\s*$/) {
513 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
517 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
520 if ($valid_lxc_conf_keys->{$key} eq 1 || $key =~ m/^lxc\.cgroup\./) {
521 push @{$conf->{lxc
}}, [$key, $value];
522 } elsif (my $errmsg = $valid_lxc_conf_keys->{$key}) {
523 warn "vm $vmid - $key: $errmsg\n";
525 warn "vm $vmid - unable to parse config: $line\n";
527 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
528 $descr .= PVE
::Tools
::decode_text
($2);
529 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
530 $conf->{snapstate
} = $1;
531 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
534 eval { $value = check_type
($key, $value); };
535 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
536 $conf->{$key} = $value;
538 warn "vm $vmid - unable to parse config: $line\n";
542 $conf->{description
} = $descr if $descr;
544 delete $res->{snapstate
}; # just to be sure
550 my $vmlist = PVE
::Cluster
::get_vmlist
();
552 return $res if !$vmlist || !$vmlist->{ids
};
553 my $ids = $vmlist->{ids
};
555 foreach my $vmid (keys %$ids) {
556 next if !$vmid; # skip CT0
557 my $d = $ids->{$vmid};
558 next if !$d->{node
} || $d->{node
} ne $nodename;
559 next if !$d->{type
} || $d->{type
} ne 'lxc';
560 $res->{$vmid}->{type
} = 'lxc';
565 sub cfs_config_path
{
566 my ($vmid, $node) = @_;
568 $node = $nodename if !$node;
569 return "nodes/$node/lxc/$vmid.conf";
573 my ($vmid, $node) = @_;
575 my $cfspath = cfs_config_path
($vmid, $node);
576 return "/etc/pve/$cfspath";
580 my ($vmid, $node) = @_;
582 $node = $nodename if !$node;
583 my $cfspath = cfs_config_path
($vmid, $node);
585 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
586 die "container $vmid does not exist\n" if !defined($conf);
592 my ($vmid, $conf) = @_;
594 my $dir = "/etc/pve/nodes/$nodename/lxc";
597 write_config
($vmid, $conf);
603 unlink config_file
($vmid, $nodename);
607 my ($vmid, $conf) = @_;
609 my $cfspath = cfs_config_path
($vmid);
611 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
614 # flock: we use one file handle per process, so lock file
615 # can be called multiple times and will succeed for the same process.
617 my $lock_handles = {};
618 my $lockdir = "/run/lock/lxc";
623 return "$lockdir/pve-config-${vmid}.lock";
627 my ($vmid, $timeout) = @_;
629 $timeout = 10 if !$timeout;
632 my $filename = lock_filename
($vmid);
634 mkdir $lockdir if !-d
$lockdir;
636 my $lock_func = sub {
637 if (!$lock_handles->{$$}->{$filename}) {
638 my $fh = new IO
::File
(">>$filename") ||
639 die "can't open file - $!\n";
640 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
643 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
644 print STDERR
"trying to aquire lock...";
647 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
648 # try again on EINTR (see bug #273)
649 if ($success || ($! != EINTR
)) {
654 print STDERR
" failed\n";
655 die "can't aquire lock - $!\n";
658 print STDERR
" OK\n";
661 $lock_handles->{$$}->{$filename}->{refcount
}++;
664 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
667 die "can't lock file '$filename' - $err";
674 my $filename = lock_filename
($vmid);
676 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
677 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
678 if ($refcount <= 0) {
679 $lock_handles->{$$}->{$filename} = undef;
686 my ($vmid, $timeout, $code, @param) = @_;
690 lock_aquire
($vmid, $timeout);
691 eval { $res = &$code(@param) };
703 return defined($confdesc->{$name});
706 # add JSON properties for create and set function
707 sub json_config_properties
{
710 foreach my $opt (keys %$confdesc) {
711 next if $opt eq 'parent' || $opt eq 'snaptime';
712 next if $prop->{$opt};
713 $prop->{$opt} = $confdesc->{$opt};
719 sub json_config_properties_no_rootfs
{
722 foreach my $opt (keys %$confdesc) {
723 next if $prop->{$opt};
724 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
725 $prop->{$opt} = $confdesc->{$opt};
731 # container status helpers
733 sub list_active_containers
{
735 my $filename = "/proc/net/unix";
737 # similar test is used by lcxcontainers.c: list_active_containers
740 my $fh = IO
::File-
>new ($filename, "r");
743 while (defined(my $line = <$fh>)) {
744 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
746 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
757 # warning: this is slow
761 my $active_hash = list_active_containers
();
763 return 1 if defined($active_hash->{$vmid});
768 sub get_container_disk_usage
{
769 my ($vmid, $pid) = @_;
771 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
774 my $last_proc_vmid_stat;
776 my $parse_cpuacct_stat = sub {
779 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
783 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
796 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
798 my $active_hash = list_active_containers
();
800 my $cpucount = $cpuinfo->{cpus
} || 1;
802 my $cdtime = gettimeofday
;
804 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
806 foreach my $vmid (keys %$list) {
807 my $d = $list->{$vmid};
809 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
810 warn $@ if $@; # ignore errors (consider them stopped)
812 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
814 my $cfspath = cfs_config_path
($vmid);
815 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
817 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
818 $d->{name
} =~ s/[\s]//g;
820 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
823 my $res = get_container_disk_usage
($vmid, $d->{pid
});
824 $d->{disk
} = $res->{used
};
825 $d->{maxdisk
} = $res->{total
};
828 # use 4GB by default ??
829 if (my $rootfs = $conf->{rootfs
}) {
830 my $rootinfo = parse_ct_rootfs
($rootfs);
831 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
833 $d->{maxdisk
} = 4*1024*1024*1024;
839 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
840 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
851 $d->{template
} = is_template
($conf);
854 foreach my $vmid (keys %$list) {
855 my $d = $list->{$vmid};
858 next if !$pid; # skip stopped CTs
860 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
861 $d->{uptime
} = time - $ctime; # the method lxcfs uses
863 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
864 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
866 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
867 my @bytes = split(/\n/, $blkio_bytes);
868 foreach my $byte (@bytes) {
869 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
870 $d->{diskread
} = $2 if $key eq 'Read';
871 $d->{diskwrite
} = $2 if $key eq 'Write';
875 my $pstat = &$parse_cpuacct_stat($vmid);
877 my $used = $pstat->{utime} + $pstat->{stime
};
879 my $old = $last_proc_vmid_stat->{$vmid};
881 $last_proc_vmid_stat->{$vmid} = {
889 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
892 my $dutime = $used - $old->{used
};
894 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
895 $last_proc_vmid_stat->{$vmid} = {
901 $d->{cpu
} = $old->{cpu
};
905 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
907 foreach my $dev (keys %$netdev) {
908 next if $dev !~ m/^veth([1-9]\d*)i/;
910 my $d = $list->{$vmid};
914 $d->{netout
} += $netdev->{$dev}->{receive
};
915 $d->{netin
} += $netdev->{$dev}->{transmit
};
922 sub classify_mountpoint
{
925 return 'device' if $vol =~ m!^/dev/!;
931 my $parse_ct_mountpoint_full = sub {
932 my ($desc, $data, $noerr) = @_;
937 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
939 return undef if $noerr;
943 if (defined(my $size = $res->{size
})) {
944 $size = PVE
::JSONSchema
::parse_size
($size);
945 if (!defined($size)) {
946 return undef if $noerr;
947 die "invalid size: $size\n";
949 $res->{size
} = $size;
952 $res->{type
} = classify_mountpoint
($res->{volume
});
957 sub parse_ct_rootfs
{
958 my ($data, $noerr) = @_;
960 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
962 $res->{mp
} = '/' if defined($res);
967 sub parse_ct_mountpoint
{
968 my ($data, $noerr) = @_;
970 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
973 sub print_ct_mountpoint
{
974 my ($info, $nomp) = @_;
975 my $skip = [ 'type' ];
976 push @$skip, 'mp' if $nomp;
977 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
980 sub print_lxc_network
{
982 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
985 sub parse_lxc_network
{
990 return $res if !$data;
992 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
994 $res->{type
} = 'veth';
995 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
1000 sub read_cgroup_value
{
1001 my ($group, $vmid, $name, $full) = @_;
1003 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
1005 return PVE
::Tools
::file_get_contents
($path) if $full;
1007 return PVE
::Tools
::file_read_firstline
($path);
1010 sub write_cgroup_value
{
1011 my ($group, $vmid, $name, $value) = @_;
1013 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
1014 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
1018 sub find_lxc_console_pids
{
1022 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1025 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1026 return if !$cmdline;
1028 my @args = split(/\0/, $cmdline);
1030 # search for lxc-console -n <vmid>
1031 return if scalar(@args) != 3;
1032 return if $args[1] ne '-n';
1033 return if $args[2] !~ m/^\d+$/;
1034 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1036 my $vmid = $args[2];
1038 push @{$res->{$vmid}}, $pid;
1050 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1052 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1054 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1059 # Note: we cannot use Net:IP, because that only allows strict
1061 sub parse_ipv4_cidr
{
1062 my ($cidr, $noerr) = @_;
1064 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1065 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1068 return undef if $noerr;
1070 die "unable to parse ipv4 address/mask\n";
1076 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1079 sub check_protection
{
1080 my ($vm_conf, $err_msg) = @_;
1082 if ($vm_conf->{protection
}) {
1083 die "$err_msg - protection mode enabled\n";
1087 sub update_lxc_config
{
1088 my ($storage_cfg, $vmid, $conf) = @_;
1090 my $dir = "/var/lib/lxc/$vmid";
1092 if ($conf->{template
}) {
1094 unlink "$dir/config";
1101 die "missing 'arch' - internal error" if !$conf->{arch
};
1102 $raw .= "lxc.arch = $conf->{arch}\n";
1104 my $unprivileged = $conf->{unprivileged
};
1105 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1107 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1108 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1109 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1110 if ($unprivileged || $custom_idmap) {
1111 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1114 die "implement me (ostype $ostype)";
1117 $raw .= "lxc.monitor.unshare = 1\n";
1119 # Should we read them from /etc/subuid?
1120 if ($unprivileged && !$custom_idmap) {
1121 $raw .= "lxc.id_map = u 0 100000 65536\n";
1122 $raw .= "lxc.id_map = g 0 100000 65536\n";
1125 if (!has_dev_console
($conf)) {
1126 $raw .= "lxc.console = none\n";
1127 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1130 my $ttycount = get_tty_count
($conf);
1131 $raw .= "lxc.tty = $ttycount\n";
1133 # some init scripts expect a linux terminal (turnkey).
1134 $raw .= "lxc.environment = TERM=linux\n";
1136 my $utsname = $conf->{hostname
} || "CT$vmid";
1137 $raw .= "lxc.utsname = $utsname\n";
1139 my $memory = $conf->{memory
} || 512;
1140 my $swap = $conf->{swap
} // 0;
1142 my $lxcmem = int($memory*1024*1024);
1143 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1145 my $lxcswap = int(($memory + $swap)*1024*1024);
1146 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1148 if (my $cpulimit = $conf->{cpulimit
}) {
1149 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1150 my $value = int(100000*$cpulimit);
1151 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1154 my $shares = $conf->{cpuunits
} || 1024;
1155 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1157 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1159 $raw .= "lxc.rootfs = $dir/rootfs\n";
1162 foreach my $k (keys %$conf) {
1163 next if $k !~ m/^net(\d+)$/;
1165 my $d = parse_lxc_network
($conf->{$k});
1167 $raw .= "lxc.network.type = veth\n";
1168 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1169 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1170 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1171 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1174 if (my $lxcconf = $conf->{lxc
}) {
1175 foreach my $entry (@$lxcconf) {
1176 my ($k, $v) = @$entry;
1177 $netcount++ if $k eq 'lxc.network.type';
1178 $raw .= "$k = $v\n";
1182 $raw .= "lxc.network.type = empty\n" if !$netcount;
1184 File
::Path
::mkpath
("$dir/rootfs");
1186 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1189 # verify and cleanup nameserver list (replace \0 with ' ')
1190 sub verify_nameserver_list
{
1191 my ($nameserver_list) = @_;
1194 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1195 PVE
::JSONSchema
::pve_verify_ip
($server);
1196 push @list, $server;
1199 return join(' ', @list);
1202 sub verify_searchdomain_list
{
1203 my ($searchdomain_list) = @_;
1206 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1207 # todo: should we add checks for valid dns domains?
1208 push @list, $server;
1211 return join(' ', @list);
1214 sub add_unused_volume
{
1215 my ($config, $volid) = @_;
1218 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1219 my $test = "unused$ind";
1220 if (my $vid = $config->{$test}) {
1221 return if $vid eq $volid; # do not add duplicates
1227 die "Too many unused volumes - please delete them first.\n" if !$key;
1229 $config->{$key} = $volid;
1234 sub update_pct_config
{
1235 my ($vmid, $conf, $running, $param, $delete) = @_;
1240 my @deleted_volumes;
1244 my $pid = find_lxc_pid
($vmid);
1245 $rootdir = "/proc/$pid/root";
1248 my $hotplug_error = sub {
1250 push @nohotplug, @_;
1257 if (defined($delete)) {
1258 foreach my $opt (@$delete) {
1259 if (!exists($conf->{$opt})) {
1260 warn "no such option: $opt\n";
1264 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1265 die "unable to delete required option '$opt'\n";
1266 } elsif ($opt eq 'swap') {
1267 delete $conf->{$opt};
1268 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1269 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1270 delete $conf->{$opt};
1271 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1272 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1273 next if $hotplug_error->($opt);
1274 delete $conf->{$opt};
1275 } elsif ($opt =~ m/^net(\d)$/) {
1276 delete $conf->{$opt};
1279 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1280 } elsif ($opt eq 'protection') {
1281 delete $conf->{$opt};
1282 } elsif ($opt =~ m/^unused(\d+)$/) {
1283 next if $hotplug_error->($opt);
1284 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1285 push @deleted_volumes, $conf->{$opt};
1286 delete $conf->{$opt};
1287 } elsif ($opt =~ m/^mp(\d+)$/) {
1288 next if $hotplug_error->($opt);
1289 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1290 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1291 if ($mountpoint->{type
} eq 'volume') {
1292 add_unused_volume
($conf, $mountpoint->{volume
})
1294 delete $conf->{$opt};
1295 } elsif ($opt eq 'unprivileged') {
1296 die "unable to delete read-only option: '$opt'\n";
1298 die "implement me (delete: $opt)"
1300 write_config
($vmid, $conf) if $running;
1304 # There's no separate swap size to configure, there's memory and "total"
1305 # memory (iow. memory+swap). This means we have to change them together.
1306 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1307 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1308 if (defined($wanted_memory) || defined($wanted_swap)) {
1310 $wanted_memory //= ($conf->{memory
} || 512);
1311 $wanted_swap //= ($conf->{swap
} || 0);
1313 my $total = $wanted_memory + $wanted_swap;
1315 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1316 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1318 $conf->{memory
} = $wanted_memory;
1319 $conf->{swap
} = $wanted_swap;
1321 write_config
($vmid, $conf) if $running;
1324 foreach my $opt (keys %$param) {
1325 my $value = $param->{$opt};
1326 if ($opt eq 'hostname') {
1327 $conf->{$opt} = $value;
1328 } elsif ($opt eq 'onboot') {
1329 $conf->{$opt} = $value ?
1 : 0;
1330 } elsif ($opt eq 'startup') {
1331 $conf->{$opt} = $value;
1332 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1333 next if $hotplug_error->($opt);
1334 $conf->{$opt} = $value;
1335 } elsif ($opt eq 'nameserver') {
1336 next if $hotplug_error->($opt);
1337 my $list = verify_nameserver_list
($value);
1338 $conf->{$opt} = $list;
1339 } elsif ($opt eq 'searchdomain') {
1340 next if $hotplug_error->($opt);
1341 my $list = verify_searchdomain_list
($value);
1342 $conf->{$opt} = $list;
1343 } elsif ($opt eq 'cpulimit') {
1344 next if $hotplug_error->($opt); # FIXME: hotplug
1345 $conf->{$opt} = $value;
1346 } elsif ($opt eq 'cpuunits') {
1347 $conf->{$opt} = $value;
1348 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1349 } elsif ($opt eq 'description') {
1350 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1351 } elsif ($opt =~ m/^net(\d+)$/) {
1353 my $net = parse_lxc_network
($value);
1355 $conf->{$opt} = print_lxc_network
($net);
1357 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1359 } elsif ($opt eq 'protection') {
1360 $conf->{$opt} = $value ?
1 : 0;
1361 } elsif ($opt =~ m/^mp(\d+)$/) {
1362 next if $hotplug_error->($opt);
1363 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1364 $conf->{$opt} = $value;
1366 } elsif ($opt eq 'rootfs') {
1367 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1368 die "implement me: $opt";
1369 } elsif ($opt eq 'unprivileged') {
1370 die "unable to modify read-only option: '$opt'\n";
1372 die "implement me: $opt";
1374 write_config
($vmid, $conf) if $running;
1377 if (@deleted_volumes) {
1378 my $storage_cfg = PVE
::Storage
::config
();
1379 foreach my $volume (@deleted_volumes) {
1380 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1385 my $storage_cfg = PVE
::Storage
::config
();
1386 create_disks
($storage_cfg, $vmid, $conf, $conf);
1389 # This should be the last thing we do here
1390 if ($running && scalar(@nohotplug)) {
1391 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1395 sub has_dev_console
{
1398 return !(defined($conf->{console
}) && !$conf->{console
});
1404 return $conf->{tty
} // $confdesc->{tty
}->{default};
1410 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1413 sub get_console_command
{
1414 my ($vmid, $conf) = @_;
1416 my $cmode = get_cmode
($conf);
1418 if ($cmode eq 'console') {
1419 return ['lxc-console', '-n', $vmid, '-t', 0];
1420 } elsif ($cmode eq 'tty') {
1421 return ['lxc-console', '-n', $vmid];
1422 } elsif ($cmode eq 'shell') {
1423 return ['lxc-attach', '--clear-env', '-n', $vmid];
1425 die "internal error";
1429 sub get_primary_ips
{
1432 # return data from net0
1434 return undef if !defined($conf->{net0
});
1435 my $net = parse_lxc_network
($conf->{net0
});
1437 my $ipv4 = $net->{ip
};
1439 if ($ipv4 =~ /^(dhcp|manual)$/) {
1445 my $ipv6 = $net->{ip6
};
1447 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1454 return ($ipv4, $ipv6);
1457 sub delete_mountpoint_volume
{
1458 my ($storage_cfg, $vmid, $volume) = @_;
1460 return if classify_mountpoint
($volume) ne 'volume';
1462 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1463 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1466 sub destroy_lxc_container
{
1467 my ($storage_cfg, $vmid, $conf) = @_;
1469 foreach_mountpoint
($conf, sub {
1470 my ($ms, $mountpoint) = @_;
1471 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1474 rmdir "/var/lib/lxc/$vmid/rootfs";
1475 unlink "/var/lib/lxc/$vmid/config";
1476 rmdir "/var/lib/lxc/$vmid";
1477 destroy_config
($vmid);
1479 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1480 #PVE::Tools::run_command($cmd);
1483 sub vm_stop_cleanup
{
1484 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1489 my $vollist = get_vm_volumes
($conf);
1490 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1493 warn $@ if $@; # avoid errors - just warn
1496 my $safe_num_ne = sub {
1499 return 0 if !defined($a) && !defined($b);
1500 return 1 if !defined($a);
1501 return 1 if !defined($b);
1506 my $safe_string_ne = sub {
1509 return 0 if !defined($a) && !defined($b);
1510 return 1 if !defined($a);
1511 return 1 if !defined($b);
1517 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1519 if ($newnet->{type
} ne 'veth') {
1520 # for when there are physical interfaces
1521 die "cannot update interface of type $newnet->{type}";
1524 my $veth = "veth${vmid}i${netid}";
1525 my $eth = $newnet->{name
};
1527 if (my $oldnetcfg = $conf->{$opt}) {
1528 my $oldnet = parse_lxc_network
($oldnetcfg);
1530 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1531 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1533 PVE
::Network
::veth_delete
($veth);
1534 delete $conf->{$opt};
1535 write_config
($vmid, $conf);
1537 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1539 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1540 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1541 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1543 if ($oldnet->{bridge
}) {
1544 PVE
::Network
::tap_unplug
($veth);
1545 foreach (qw(bridge tag firewall)) {
1546 delete $oldnet->{$_};
1548 $conf->{$opt} = print_lxc_network
($oldnet);
1549 write_config
($vmid, $conf);
1552 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1553 foreach (qw(bridge tag firewall)) {
1554 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1556 $conf->{$opt} = print_lxc_network
($oldnet);
1557 write_config
($vmid, $conf);
1560 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1563 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1567 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1569 my $veth = "veth${vmid}i${netid}";
1570 my $vethpeer = $veth . "p";
1571 my $eth = $newnet->{name
};
1573 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1574 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1576 # attach peer in container
1577 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1578 PVE
::Tools
::run_command
($cmd);
1580 # link up peer in container
1581 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1582 PVE
::Tools
::run_command
($cmd);
1584 my $done = { type
=> 'veth' };
1585 foreach (qw(bridge tag firewall hwaddr name)) {
1586 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1588 $conf->{$opt} = print_lxc_network
($done);
1590 write_config
($vmid, $conf);
1593 sub update_ipconfig
{
1594 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1596 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1598 my $optdata = parse_lxc_network
($conf->{$opt});
1602 my $cmdargs = shift;
1603 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1605 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1607 my $change_ip_config = sub {
1608 my ($ipversion) = @_;
1610 my $family_opt = "-$ipversion";
1611 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1612 my $gw= "gw$suffix";
1613 my $ip= "ip$suffix";
1615 my $newip = $newnet->{$ip};
1616 my $newgw = $newnet->{$gw};
1617 my $oldip = $optdata->{$ip};
1619 my $change_ip = &$safe_string_ne($oldip, $newip);
1620 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1622 return if !$change_ip && !$change_gw;
1624 # step 1: add new IP, if this fails we cancel
1625 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1626 if ($change_ip && $is_real_ip) {
1627 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1634 # step 2: replace gateway
1635 # If this fails we delete the added IP and cancel.
1636 # If it succeeds we save the config and delete the old IP, ignoring
1637 # errors. The config is then saved.
1638 # Note: 'ip route replace' can add
1642 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1643 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1645 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1649 # the route was not replaced, the old IP is still available
1650 # rollback (delete new IP) and cancel
1652 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1653 warn $@ if $@; # no need to die here
1658 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1659 # if the route was not deleted, the guest might have deleted it manually
1665 # from this point on we save the configuration
1666 # step 3: delete old IP ignoring errors
1667 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1668 # We need to enable promote_secondaries, otherwise our newly added
1669 # address will be removed along with the old one.
1672 if ($ipversion == 4) {
1673 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1674 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1675 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1677 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1679 warn $@ if $@; # no need to die here
1681 if ($ipversion == 4) {
1682 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1686 foreach my $property ($ip, $gw) {
1687 if ($newnet->{$property}) {
1688 $optdata->{$property} = $newnet->{$property};
1690 delete $optdata->{$property};
1693 $conf->{$opt} = print_lxc_network
($optdata);
1694 write_config
($vmid, $conf);
1695 $lxc_setup->setup_network($conf);
1698 &$change_ip_config(4);
1699 &$change_ip_config(6);
1703 # Internal snapshots
1705 # NOTE: Snapshot create/delete involves several non-atomic
1706 # actions, and can take a long time.
1707 # So we try to avoid locking the file and use the 'lock' variable
1708 # inside the config file instead.
1710 my $snapshot_copy_config = sub {
1711 my ($source, $dest) = @_;
1713 foreach my $k (keys %$source) {
1714 next if $k eq 'snapshots';
1715 next if $k eq 'snapstate';
1716 next if $k eq 'snaptime';
1717 next if $k eq 'vmstate';
1718 next if $k eq 'lock';
1719 next if $k eq 'digest';
1720 next if $k eq 'description';
1722 $dest->{$k} = $source->{$k};
1726 my $snapshot_prepare = sub {
1727 my ($vmid, $snapname, $comment) = @_;
1731 my $updatefn = sub {
1733 my $conf = load_config
($vmid);
1735 die "you can't take a snapshot if it's a template\n"
1736 if is_template
($conf);
1740 $conf->{lock} = 'snapshot';
1742 die "snapshot name '$snapname' already used\n"
1743 if defined($conf->{snapshots
}->{$snapname});
1745 my $storecfg = PVE
::Storage
::config
();
1746 my $feature = $snapname eq 'vzdump' ?
'vzdump' : 'snapshot';
1747 die "snapshot feature is not available\n" if !has_feature
($feature, $conf, $storecfg);
1749 $snap = $conf->{snapshots
}->{$snapname} = {};
1751 &$snapshot_copy_config($conf, $snap);
1753 $snap->{'snapstate'} = "prepare";
1754 $snap->{'snaptime'} = time();
1755 $snap->{'description'} = $comment if $comment;
1756 $conf->{snapshots
}->{$snapname} = $snap;
1758 write_config
($vmid, $conf);
1761 lock_container
($vmid, 10, $updatefn);
1766 my $snapshot_commit = sub {
1767 my ($vmid, $snapname) = @_;
1769 my $updatefn = sub {
1771 my $conf = load_config
($vmid);
1773 die "missing snapshot lock\n"
1774 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1776 die "snapshot '$snapname' does not exist\n"
1777 if !defined($conf->{snapshots
}->{$snapname});
1779 die "wrong snapshot state\n"
1780 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1781 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1783 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1784 delete $conf->{lock};
1785 $conf->{parent
} = $snapname;
1787 write_config
($vmid, $conf);
1790 lock_container
($vmid, 10 ,$updatefn);
1794 my ($feature, $conf, $storecfg, $snapname) = @_;
1797 my $vzdump = $feature eq 'vzdump';
1798 $feature = 'snapshot' if $vzdump;
1800 foreach_mountpoint
($conf, sub {
1801 my ($ms, $mountpoint) = @_;
1803 return if $err; # skip further test
1804 return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup
};
1806 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1808 # TODO: implement support for mountpoints
1809 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1813 return $err ?
0 : 1;
1816 sub snapshot_create
{
1817 my ($vmid, $snapname, $comment) = @_;
1819 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1821 my $conf = load_config
($vmid);
1823 my $running = check_running
($vmid);
1829 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1831 PVE
::Tools
::run_command
(['/bin/sync']);
1834 my $storecfg = PVE
::Storage
::config
();
1835 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1836 my $volid = $rootinfo->{volume
};
1838 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1839 &$snapshot_commit($vmid, $snapname);
1844 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1849 snapshot_delete
($vmid, $snapname, 1);
1854 sub snapshot_delete
{
1855 my ($vmid, $snapname, $force) = @_;
1861 my $updatefn = sub {
1863 $conf = load_config
($vmid);
1865 die "you can't delete a snapshot if vm is a template\n"
1866 if is_template
($conf);
1868 $snap = $conf->{snapshots
}->{$snapname};
1872 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1874 $snap->{snapstate
} = 'delete';
1876 write_config
($vmid, $conf);
1879 lock_container
($vmid, 10, $updatefn);
1881 my $storecfg = PVE
::Storage
::config
();
1883 my $unlink_parent = sub {
1885 my ($confref, $new_parent) = @_;
1887 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
1889 $confref->{parent
} = $new_parent;
1891 delete $confref->{parent
};
1896 my $del_snap = sub {
1900 my $parent = $conf->{snapshots
}->{$snapname}->{parent
};
1901 foreach my $snapkey (keys %{$conf->{snapshots
}}) {
1902 &$unlink_parent($conf->{snapshots
}->{$snapkey}, $parent);
1905 &$unlink_parent($conf, $parent);
1907 delete $conf->{snapshots
}->{$snapname};
1909 write_config
($vmid, $conf);
1912 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1913 my $rootinfo = parse_ct_rootfs
($rootfs);
1914 my $volid = $rootinfo->{volume
};
1917 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1921 if(!$err || ($err && $force)) {
1922 lock_container
($vmid, 10, $del_snap);
1924 die "Can't delete snapshot: $vmid $snapname $err\n";
1929 sub snapshot_rollback
{
1930 my ($vmid, $snapname) = @_;
1932 my $storecfg = PVE
::Storage
::config
();
1934 my $conf = load_config
($vmid);
1936 die "you can't rollback if vm is a template\n" if is_template
($conf);
1938 my $snap = $conf->{snapshots
}->{$snapname};
1940 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1942 my $rootfs = $snap->{rootfs
};
1943 my $rootinfo = parse_ct_rootfs
($rootfs);
1944 my $volid = $rootinfo->{volume
};
1946 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1948 my $updatefn = sub {
1950 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1951 if $snap->{snapstate
};
1955 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1957 die "unable to rollback vm $vmid: vm is running\n"
1958 if check_running
($vmid);
1960 $conf->{lock} = 'rollback';
1964 # copy snapshot config to current config
1966 my $tmp_conf = $conf;
1967 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1968 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1969 delete $conf->{snaptime
};
1970 delete $conf->{snapname
};
1971 $conf->{parent
} = $snapname;
1973 write_config
($vmid, $conf);
1976 my $unlockfn = sub {
1977 delete $conf->{lock};
1978 write_config
($vmid, $conf);
1981 lock_container
($vmid, 10, $updatefn);
1983 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1985 lock_container
($vmid, 5, $unlockfn);
1988 sub template_create
{
1989 my ($vmid, $conf) = @_;
1991 my $storecfg = PVE
::Storage
::config
();
1993 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
1994 my $volid = $rootinfo->{volume
};
1996 die "Template feature is not available for '$volid'\n"
1997 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1999 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2001 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2002 $rootinfo->{volume
} = $template_volid;
2003 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2005 write_config
($vmid, $conf);
2011 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2014 sub mountpoint_names
{
2017 my @names = ('rootfs');
2019 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2020 push @names, "mp$i";
2023 return $reverse ?
reverse @names : @names;
2026 # The container might have *different* symlinks than the host. realpath/abs_path
2027 # use the actual filesystem to resolve links.
2028 sub sanitize_mountpoint
{
2030 $mp = '/' . $mp; # we always start with a slash
2031 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
2032 $mp =~ s
@/\./@@g; # collapse /./
2033 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
2034 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
2035 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
2039 sub foreach_mountpoint_full
{
2040 my ($conf, $reverse, $func) = @_;
2042 foreach my $key (mountpoint_names
($reverse)) {
2043 my $value = $conf->{$key};
2044 next if !defined($value);
2045 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2046 next if !defined($mountpoint);
2048 $mountpoint->{mp
} = sanitize_mountpoint
($mountpoint->{mp
});
2050 my $path = $mountpoint->{volume
};
2051 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2053 &$func($key, $mountpoint);
2057 sub foreach_mountpoint
{
2058 my ($conf, $func) = @_;
2060 foreach_mountpoint_full
($conf, 0, $func);
2063 sub foreach_mountpoint_reverse
{
2064 my ($conf, $func) = @_;
2066 foreach_mountpoint_full
($conf, 1, $func);
2069 sub check_ct_modify_config_perm
{
2070 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2072 return 1 if $authuser ne 'root@pam';
2074 foreach my $opt (@$key_list) {
2076 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2077 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2078 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2079 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2080 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2081 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2082 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2083 $opt eq 'searchdomain' || $opt eq 'hostname') {
2084 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2086 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2094 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2096 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2097 my $volid_list = get_vm_volumes
($conf);
2099 foreach_mountpoint_reverse
($conf, sub {
2100 my ($ms, $mountpoint) = @_;
2102 my $volid = $mountpoint->{volume
};
2103 my $mount = $mountpoint->{mp
};
2105 return if !$volid || !$mount;
2107 my $mount_path = "$rootdir/$mount";
2108 $mount_path =~ s!/+!/!g;
2110 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2113 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2126 my ($vmid, $storage_cfg, $conf) = @_;
2128 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2129 File
::Path
::make_path
($rootdir);
2131 my $volid_list = get_vm_volumes
($conf);
2132 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2135 foreach_mountpoint
($conf, sub {
2136 my ($ms, $mountpoint) = @_;
2138 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2142 warn "mounting container failed\n";
2143 umount_all
($vmid, $storage_cfg, $conf, 1);
2151 sub mountpoint_mount_path
{
2152 my ($mountpoint, $storage_cfg, $snapname) = @_;
2154 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2157 my $check_mount_path = sub {
2159 $path = File
::Spec-
>canonpath($path);
2160 my $real = Cwd
::realpath
($path);
2161 if ($real ne $path) {
2162 die "mount path modified by symlink: $path != $real";
2171 if ($line =~ m
@^(/dev/loop\d
+):@) {
2175 my $cmd = ['losetup', '--associated', $path];
2176 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2180 # use $rootdir = undef to just return the corresponding mount path
2181 sub mountpoint_mount
{
2182 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2184 my $volid = $mountpoint->{volume
};
2185 my $mount = $mountpoint->{mp
};
2186 my $type = $mountpoint->{type
};
2188 return if !$volid || !$mount;
2192 if (defined($rootdir)) {
2193 $rootdir =~ s!/+$!!;
2194 $mount_path = "$rootdir/$mount";
2195 $mount_path =~ s!/+!/!g;
2196 &$check_mount_path($mount_path);
2197 File
::Path
::mkpath
($mount_path);
2200 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2202 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2205 if (defined($mountpoint->{acl
})) {
2206 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2208 if ($mountpoint->{ro
}) {
2209 $optstring .= ',' if $optstring;
2213 my @extra_opts = ('-o', $optstring);
2217 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2218 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2220 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2221 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2223 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2225 if ($format eq 'subvol') {
2228 if ($scfg->{type
} eq 'zfspool') {
2229 my $path_arg = $path;
2230 $path_arg =~ s!^/+!!;
2231 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2233 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2236 if ($mountpoint->{ro
}) {
2237 die "read-only bind mounts not supported\n";
2239 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
2242 return wantarray ?
($path, 0) : $path;
2243 } elsif ($format eq 'raw' || $format eq 'iso') {
2244 my $use_loopdev = 0;
2245 if ($scfg->{path
}) {
2246 push @extra_opts, '-o', 'loop';
2248 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2249 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2252 die "unsupported storage type '$scfg->{type}'\n";
2255 if ($format eq 'iso') {
2256 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2257 } elsif ($isBase || defined($snapname)) {
2258 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2260 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2263 return wantarray ?
($path, $use_loopdev) : $path;
2265 die "unsupported image format '$format'\n";
2267 } elsif ($type eq 'device') {
2268 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2269 return wantarray ?
($volid, 0) : $volid;
2270 } elsif ($type eq 'bind') {
2271 if ($mountpoint->{ro
}) {
2272 die "read-only bind mounts not supported\n";
2273 # Theoretically we'd have to execute both:
2274 # mount -o bind $a $b
2275 # mount -o bind,remount,ro $a $b
2277 die "directory '$volid' does not exist\n" if ! -d
$volid;
2278 &$check_mount_path($volid);
2279 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
2280 return wantarray ?
($volid, 0) : $volid;
2283 die "unsupported storage";
2286 sub get_vm_volumes
{
2287 my ($conf, $excludes) = @_;
2291 foreach_mountpoint
($conf, sub {
2292 my ($ms, $mountpoint) = @_;
2294 return if $excludes && $ms eq $excludes;
2296 my $volid = $mountpoint->{volume
};
2298 return if !$volid || $mountpoint->{type
} ne 'volume';
2300 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2303 push @$vollist, $volid;
2310 my ($dev, $rootuid, $rootgid) = @_;
2312 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2313 '-E', "root_owner=$rootuid:$rootgid",
2318 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2320 if ($volid =~ m!^/dev/.+!) {
2325 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2327 die "cannot format volume '$volid' with no storage\n" if !$storage;
2329 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2331 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2333 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2334 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2336 die "cannot format volume '$volid' (format == $format)\n"
2337 if $format ne 'raw';
2339 mkfs
($path, $rootuid, $rootgid);
2343 my ($storecfg, $vollist) = @_;
2345 foreach my $volid (@$vollist) {
2346 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2352 my ($storecfg, $vmid, $settings, $conf) = @_;
2357 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2358 my $chown_vollist = [];
2360 foreach_mountpoint
($settings, sub {
2361 my ($ms, $mountpoint) = @_;
2363 my $volid = $mountpoint->{volume
};
2364 my $mp = $mountpoint->{mp
};
2366 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2368 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2369 my ($storeid, $size_gb) = ($1, $2);
2371 my $size_kb = int(${size_gb
}*1024) * 1024;
2373 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2374 # fixme: use better naming ct-$vmid-disk-X.raw?
2376 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2378 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2380 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2382 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2384 push @$chown_vollist, $volid;
2386 } elsif ($scfg->{type
} eq 'zfspool') {
2388 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2390 push @$chown_vollist, $volid;
2391 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2393 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2394 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2396 } elsif ($scfg->{type
} eq 'rbd') {
2398 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2399 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2400 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2402 die "unable to create containers on storage type '$scfg->{type}'\n";
2404 push @$vollist, $volid;
2405 $mountpoint->{volume
} = $volid;
2406 $mountpoint->{size
} = $size_kb * 1024;
2407 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2409 # use specified/existing volid/dir/device
2410 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2414 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2415 foreach my $volid (@$chown_vollist) {
2416 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2417 chown($rootuid, $rootgid, $path);
2419 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2421 # free allocated images on error
2423 destroy_disks
($storecfg, $vollist);
2429 # bash completion helper
2431 sub complete_os_templates
{
2432 my ($cmdname, $pname, $cvalue) = @_;
2434 my $cfg = PVE
::Storage
::config
();
2438 if ($cvalue =~ m/^([^:]+):/) {
2442 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2443 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2446 foreach my $id (keys %$data) {
2447 foreach my $item (@{$data->{$id}}) {
2448 push @$res, $item->{volid
} if defined($item->{volid
});
2455 my $complete_ctid_full = sub {
2458 my $idlist = vmstatus
();
2460 my $active_hash = list_active_containers
();
2464 foreach my $id (keys %$idlist) {
2465 my $d = $idlist->{$id};
2466 if (defined($running)) {
2467 next if $d->{template
};
2468 next if $running && !$active_hash->{$id};
2469 next if !$running && $active_hash->{$id};
2478 return &$complete_ctid_full();
2481 sub complete_ctid_stopped
{
2482 return &$complete_ctid_full(0);
2485 sub complete_ctid_running
{
2486 return &$complete_ctid_full(1);
2496 my $lxc = $conf->{lxc
};
2497 foreach my $entry (@$lxc) {
2498 my ($key, $value) = @$entry;
2499 next if $key ne 'lxc.id_map';
2500 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2501 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2502 push @$id_map, [$type, $ct, $host, $length];
2504 $rootuid = $host if $type eq 'u';
2505 $rootgid = $host if $type eq 'g';
2508 die "failed to parse id_map: $value\n";
2512 if (!@$id_map && $conf->{unprivileged
}) {
2513 # Should we read them from /etc/subuid?
2514 $id_map = [ ['u', '0', '100000', '65536'],
2515 ['g', '0', '100000', '65536'] ];
2516 $rootuid = $rootgid = 100000;
2519 return ($id_map, $rootuid, $rootgid);
2522 sub userns_command
{
2525 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];