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 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
60 type
=> 'string', format
=> $rootfs_desc,
61 description
=> "Use volume as container root.",
65 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
66 description
=> "The name of the snapshot.",
67 type
=> 'string', format
=> 'pve-configid',
75 description
=> "Lock/unlock the VM.",
76 enum
=> [qw(migrate backup snapshot rollback)],
81 description
=> "Specifies whether a VM will be started during system bootup.",
84 startup
=> get_standard_option
('pve-startup-order'),
88 description
=> "Enable/disable Template.",
94 enum
=> ['amd64', 'i386'],
95 description
=> "OS architecture type.",
101 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
102 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
107 description
=> "Attach a console device (/dev/console) to the container.",
113 description
=> "Specify the number of tty available to the container",
121 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.",
129 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.",
137 description
=> "Amount of RAM for the VM in MB.",
144 description
=> "Amount of SWAP for the VM in MB.",
150 description
=> "Set a host name for the container.",
151 type
=> 'string', format
=> 'dns-name',
157 description
=> "Container description. Only used on the configuration web interface.",
161 type
=> 'string', format
=> 'dns-name-list',
162 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
166 type
=> 'string', format
=> 'address-list',
167 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.",
169 rootfs
=> get_standard_option
('pve-ct-rootfs'),
172 type
=> 'string', format
=> 'pve-configid',
174 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
178 description
=> "Timestamp for snapshots.",
184 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).",
186 enum
=> ['shell', 'console', 'tty'],
192 description
=> "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
198 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
203 my $valid_lxc_conf_keys = {
207 'lxc.haltsignal' => 1,
208 'lxc.rebootsignal' => 1,
209 'lxc.stopsignal' => 1,
211 'lxc.network.type' => 1,
212 'lxc.network.flags' => 1,
213 'lxc.network.link' => 1,
214 'lxc.network.mtu' => 1,
215 'lxc.network.name' => 1,
216 'lxc.network.hwaddr' => 1,
217 'lxc.network.ipv4' => 1,
218 'lxc.network.ipv4.gateway' => 1,
219 'lxc.network.ipv6' => 1,
220 'lxc.network.ipv6.gateway' => 1,
221 'lxc.network.script.up' => 1,
222 'lxc.network.script.down' => 1,
224 'lxc.console.logfile' => 1,
227 'lxc.devttydir' => 1,
228 'lxc.hook.autodev' => 1,
232 'lxc.mount.entry' => 1,
233 'lxc.mount.auto' => 1,
235 'lxc.rootfs.mount' => 1,
236 'lxc.rootfs.options' => 1,
240 'lxc.aa_profile' => 1,
241 'lxc.aa_allow_incomplete' => 1,
242 'lxc.se_context' => 1,
245 'lxc.hook.pre-start' => 1,
246 'lxc.hook.pre-mount' => 1,
247 'lxc.hook.mount' => 1,
248 'lxc.hook.start' => 1,
249 'lxc.hook.stop' => 1,
250 'lxc.hook.post-stop' => 1,
251 'lxc.hook.clone' => 1,
252 'lxc.hook.destroy' => 1,
255 'lxc.start.auto' => 1,
256 'lxc.start.delay' => 1,
257 'lxc.start.order' => 1,
259 'lxc.environment' => 1,
270 description
=> "Network interface type.",
275 format_description
=> 'String',
276 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
277 pattern
=> '[-_.\w\d]+',
281 format_description
=> 'vmbr<Number>',
282 description
=> 'Bridge to attach the network device to.',
283 pattern
=> '[-_.\w\d]+',
288 format_description
=> 'MAC',
289 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
290 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
295 format_description
=> 'Number',
296 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
297 minimum
=> 64, # minimum ethernet frame is 64 bytes
302 format
=> 'pve-ipv4-config',
303 format_description
=> 'IPv4Format/CIDR',
304 description
=> 'IPv4 address in CIDR format.',
310 format_description
=> 'GatewayIPv4',
311 description
=> 'Default gateway for IPv4 traffic.',
316 format
=> 'pve-ipv6-config',
317 format_description
=> 'IPv6Format/CIDR',
318 description
=> 'IPv6 address in CIDR format.',
324 format_description
=> 'GatewayIPv6',
325 description
=> 'Default gateway for IPv6 traffic.',
330 format_description
=> '[1|0]',
331 description
=> "Controls whether this interface's firewall rules should be used.",
336 format_description
=> 'VlanNo',
339 description
=> "VLAN tag foro this interface.",
343 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
345 my $MAX_LXC_NETWORKS = 10;
346 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
347 $confdesc->{"net$i"} = {
349 type
=> 'string', format
=> $netconf_desc,
350 description
=> "Specifies network interfaces for the container.",
358 format_description
=> 'Path',
359 description
=> 'Path to the mountpoint as seen from inside the container.',
363 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
367 type
=> 'string', format
=> 'pve-volume-id',
368 description
=> "Reference to unused volumes.",
371 my $MAX_MOUNT_POINTS = 10;
372 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
373 $confdesc->{"mp$i"} = {
375 type
=> 'string', format
=> $mp_desc,
376 description
=> "Use volume as container mount point (experimental feature).",
381 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
382 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
383 $confdesc->{"unused$i"} = $unuseddesc;
386 sub write_pct_config
{
387 my ($filename, $conf) = @_;
389 delete $conf->{snapstate
}; # just to be sure
391 my $generate_raw_config = sub {
396 # add description as comment to top of file
397 my $descr = $conf->{description
} || '';
398 foreach my $cl (split(/\n/, $descr)) {
399 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
402 foreach my $key (sort keys %$conf) {
403 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
404 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
405 my $value = $conf->{$key};
406 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
407 $raw .= "$key: $value\n";
410 if (my $lxcconf = $conf->{lxc
}) {
411 foreach my $entry (@$lxcconf) {
412 my ($k, $v) = @$entry;
420 my $raw = &$generate_raw_config($conf);
422 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
423 $raw .= "\n[$snapname]\n";
424 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
431 my ($key, $value) = @_;
433 die "unknown setting '$key'\n" if !$confdesc->{$key};
435 my $type = $confdesc->{$key}->{type
};
437 if (!defined($value)) {
438 die "got undefined value\n";
441 if ($value =~ m/[\n\r]/) {
442 die "property contains a line feed\n";
445 if ($type eq 'boolean') {
446 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
447 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
448 die "type check ('boolean') failed - got '$value'\n";
449 } elsif ($type eq 'integer') {
450 return int($1) if $value =~ m/^(\d+)$/;
451 die "type check ('integer') failed - got '$value'\n";
452 } elsif ($type eq 'number') {
453 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
454 die "type check ('number') failed - got '$value'\n";
455 } elsif ($type eq 'string') {
456 if (my $fmt = $confdesc->{$key}->{format
}) {
457 PVE
::JSONSchema
::check_format
($fmt, $value);
466 sub parse_pct_config
{
467 my ($filename, $raw) = @_;
469 return undef if !defined($raw);
472 digest
=> Digest
::SHA
::sha1_hex
($raw),
476 $filename =~ m
|/lxc/(\d
+).conf
$|
477 || die "got strange filename '$filename'";
485 my @lines = split(/\n/, $raw);
486 foreach my $line (@lines) {
487 next if $line =~ m/^\s*$/;
489 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
491 $conf->{description
} = $descr if $descr;
493 $conf = $res->{snapshots
}->{$section} = {};
497 if ($line =~ m/^\#(.*)\s*$/) {
498 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
502 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
505 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
506 push @{$conf->{lxc
}}, [$key, $value];
508 warn "vm $vmid - unable to parse config: $line\n";
510 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
511 $descr .= PVE
::Tools
::decode_text
($2);
512 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
513 $conf->{snapstate
} = $1;
514 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
517 eval { $value = check_type
($key, $value); };
518 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
519 $conf->{$key} = $value;
521 warn "vm $vmid - unable to parse config: $line\n";
525 $conf->{description
} = $descr if $descr;
527 delete $res->{snapstate
}; # just to be sure
533 my $vmlist = PVE
::Cluster
::get_vmlist
();
535 return $res if !$vmlist || !$vmlist->{ids
};
536 my $ids = $vmlist->{ids
};
538 foreach my $vmid (keys %$ids) {
539 next if !$vmid; # skip CT0
540 my $d = $ids->{$vmid};
541 next if !$d->{node
} || $d->{node
} ne $nodename;
542 next if !$d->{type
} || $d->{type
} ne 'lxc';
543 $res->{$vmid}->{type
} = 'lxc';
548 sub cfs_config_path
{
549 my ($vmid, $node) = @_;
551 $node = $nodename if !$node;
552 return "nodes/$node/lxc/$vmid.conf";
556 my ($vmid, $node) = @_;
558 my $cfspath = cfs_config_path
($vmid, $node);
559 return "/etc/pve/$cfspath";
563 my ($vmid, $node) = @_;
565 $node = $nodename if !$node;
566 my $cfspath = cfs_config_path
($vmid, $node);
568 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
569 die "container $vmid does not exists\n" if !defined($conf);
575 my ($vmid, $conf) = @_;
577 my $dir = "/etc/pve/nodes/$nodename/lxc";
580 write_config
($vmid, $conf);
586 unlink config_file
($vmid, $nodename);
590 my ($vmid, $conf) = @_;
592 my $cfspath = cfs_config_path
($vmid);
594 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
597 # flock: we use one file handle per process, so lock file
598 # can be called multiple times and succeeds for the same process.
600 my $lock_handles = {};
601 my $lockdir = "/run/lock/lxc";
606 return "$lockdir/pve-config-${vmid}.lock";
610 my ($vmid, $timeout) = @_;
612 $timeout = 10 if !$timeout;
615 my $filename = lock_filename
($vmid);
617 mkdir $lockdir if !-d
$lockdir;
619 my $lock_func = sub {
620 if (!$lock_handles->{$$}->{$filename}) {
621 my $fh = new IO
::File
(">>$filename") ||
622 die "can't open file - $!\n";
623 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
626 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
627 print STDERR
"trying to aquire lock...";
630 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
631 # try again on EINTR (see bug #273)
632 if ($success || ($! != EINTR
)) {
637 print STDERR
" failed\n";
638 die "can't aquire lock - $!\n";
641 print STDERR
" OK\n";
644 $lock_handles->{$$}->{$filename}->{refcount
}++;
647 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
650 die "can't lock file '$filename' - $err";
657 my $filename = lock_filename
($vmid);
659 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
660 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
661 if ($refcount <= 0) {
662 $lock_handles->{$$}->{$filename} = undef;
669 my ($vmid, $timeout, $code, @param) = @_;
673 lock_aquire
($vmid, $timeout);
674 eval { $res = &$code(@param) };
686 return defined($confdesc->{$name});
689 # add JSON properties for create and set function
690 sub json_config_properties
{
693 foreach my $opt (keys %$confdesc) {
694 next if $opt eq 'parent' || $opt eq 'snaptime';
695 next if $prop->{$opt};
696 $prop->{$opt} = $confdesc->{$opt};
702 sub json_config_properties_no_rootfs
{
705 foreach my $opt (keys %$confdesc) {
706 next if $prop->{$opt};
707 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
708 $prop->{$opt} = $confdesc->{$opt};
714 # container status helpers
716 sub list_active_containers
{
718 my $filename = "/proc/net/unix";
720 # similar test is used by lcxcontainers.c: list_active_containers
723 my $fh = IO
::File-
>new ($filename, "r");
726 while (defined(my $line = <$fh>)) {
727 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
729 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
740 # warning: this is slow
744 my $active_hash = list_active_containers
();
746 return 1 if defined($active_hash->{$vmid});
751 sub get_container_disk_usage
{
752 my ($vmid, $pid) = @_;
754 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
757 my $last_proc_vmid_stat;
759 my $parse_cpuacct_stat = sub {
762 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
766 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
779 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
781 my $active_hash = list_active_containers
();
783 my $cpucount = $cpuinfo->{cpus
} || 1;
785 my $cdtime = gettimeofday
;
787 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
789 foreach my $vmid (keys %$list) {
790 my $d = $list->{$vmid};
792 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
793 warn $@ if $@; # ignore errors (consider them stopped)
795 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
797 my $cfspath = cfs_config_path
($vmid);
798 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
800 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
801 $d->{name
} =~ s/[\s]//g;
803 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
806 my $res = get_container_disk_usage
($vmid, $d->{pid
});
807 $d->{disk
} = $res->{used
};
808 $d->{maxdisk
} = $res->{total
};
811 # use 4GB by default ??
812 if (my $rootfs = $conf->{rootfs
}) {
813 my $rootinfo = parse_ct_mountpoint
($rootfs);
814 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
816 $d->{maxdisk
} = 4*1024*1024*1024;
822 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
823 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
834 $d->{template
} = is_template
($conf);
837 foreach my $vmid (keys %$list) {
838 my $d = $list->{$vmid};
841 next if !$pid; # skip stopped CTs
843 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
844 $d->{uptime
} = time - $ctime; # the method lxcfs uses
846 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
847 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
849 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
850 my @bytes = split(/\n/, $blkio_bytes);
851 foreach my $byte (@bytes) {
852 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
853 $d->{diskread
} = $2 if $key eq 'Read';
854 $d->{diskwrite
} = $2 if $key eq 'Write';
858 my $pstat = &$parse_cpuacct_stat($vmid);
860 my $used = $pstat->{utime} + $pstat->{stime
};
862 my $old = $last_proc_vmid_stat->{$vmid};
864 $last_proc_vmid_stat->{$vmid} = {
872 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
875 my $dutime = $used - $old->{used
};
877 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
878 $last_proc_vmid_stat->{$vmid} = {
884 $d->{cpu
} = $old->{cpu
};
888 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
890 foreach my $dev (keys %$netdev) {
891 next if $dev !~ m/^veth([1-9]\d*)i/;
893 my $d = $list->{$vmid};
897 $d->{netout
} += $netdev->{$dev}->{receive
};
898 $d->{netin
} += $netdev->{$dev}->{transmit
};
905 sub classify_mountpoint
{
908 return 'device' if $vol =~ m!^/dev/!;
914 sub parse_ct_mountpoint
{
915 my ($data, $noerr) = @_;
920 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
922 return undef if $noerr;
926 if (defined(my $size = $res->{size
})) {
927 $size = PVE
::JSONSchema
::parse_size
($size);
928 if (!defined($size)) {
929 return undef if $noerr;
930 die "invalid size: $size\n";
932 $res->{size
} = $size;
935 $res->{type
} = classify_mountpoint
($res->{volume
});
940 sub print_ct_mountpoint
{
941 my ($info, $nomp) = @_;
942 my $skip = [ 'type' ];
943 push @$skip, 'mp' if $nomp;
944 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
947 sub print_lxc_network
{
949 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
952 sub parse_lxc_network
{
957 return $res if !$data;
959 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
961 $res->{type
} = 'veth';
962 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
967 sub read_cgroup_value
{
968 my ($group, $vmid, $name, $full) = @_;
970 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
972 return PVE
::Tools
::file_get_contents
($path) if $full;
974 return PVE
::Tools
::file_read_firstline
($path);
977 sub write_cgroup_value
{
978 my ($group, $vmid, $name, $value) = @_;
980 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
981 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
985 sub find_lxc_console_pids
{
989 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
992 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
995 my @args = split(/\0/, $cmdline);
997 # serach for lxc-console -n <vmid>
998 return if scalar(@args) != 3;
999 return if $args[1] ne '-n';
1000 return if $args[2] !~ m/^\d+$/;
1001 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1003 my $vmid = $args[2];
1005 push @{$res->{$vmid}}, $pid;
1017 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1019 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1021 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1026 # Note: we cannot use Net:IP, because that only allows strict
1028 sub parse_ipv4_cidr
{
1029 my ($cidr, $noerr) = @_;
1031 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1032 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1035 return undef if $noerr;
1037 die "unable to parse ipv4 address/mask\n";
1043 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1046 sub check_protection
{
1047 my ($vm_conf, $err_msg) = @_;
1049 if ($vm_conf->{protection
}) {
1050 die "$err_msg - protection mode enabled\n";
1054 sub update_lxc_config
{
1055 my ($storage_cfg, $vmid, $conf) = @_;
1057 my $dir = "/var/lib/lxc/$vmid";
1059 if ($conf->{template
}) {
1061 unlink "$dir/config";
1068 die "missing 'arch' - internal error" if !$conf->{arch
};
1069 $raw .= "lxc.arch = $conf->{arch}\n";
1071 my $unprivileged = $conf->{unprivileged
};
1072 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1074 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1075 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1076 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1077 if ($unprivileged || $custom_idmap) {
1078 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1084 $raw .= "lxc.start.unshare = 1\n";
1086 # Should we read them from /etc/subuid?
1087 if ($unprivileged && !$custom_idmap) {
1088 $raw .= "lxc.id_map = u 0 100000 65536\n";
1089 $raw .= "lxc.id_map = g 0 100000 65536\n";
1092 if (!has_dev_console
($conf)) {
1093 $raw .= "lxc.console = none\n";
1094 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1097 my $ttycount = get_tty_count
($conf);
1098 $raw .= "lxc.tty = $ttycount\n";
1100 # some init scripts expects a linux terminal (turnkey).
1101 $raw .= "lxc.environment = TERM=linux\n";
1103 my $utsname = $conf->{hostname
} || "CT$vmid";
1104 $raw .= "lxc.utsname = $utsname\n";
1106 my $memory = $conf->{memory
} || 512;
1107 my $swap = $conf->{swap
} // 0;
1109 my $lxcmem = int($memory*1024*1024);
1110 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1111 $raw .= "lxc.cgroup.memory.kmem.limit_in_bytes = $lxcmem\n";
1113 my $lxcswap = int(($memory + $swap)*1024*1024);
1114 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1116 if (my $cpulimit = $conf->{cpulimit
}) {
1117 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1118 my $value = int(100000*$cpulimit);
1119 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1122 my $shares = $conf->{cpuunits
} || 1024;
1123 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1125 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1126 $mountpoint->{mp
} = '/';
1128 $raw .= "lxc.rootfs = $dir/rootfs\n";
1129 $raw .= "lxc.hook.stop = /usr/lib/x86_64-linux-gnu/lxc/hooks/unmount-namespace\n";
1132 foreach my $k (keys %$conf) {
1133 next if $k !~ m/^net(\d+)$/;
1135 my $d = parse_lxc_network
($conf->{$k});
1137 $raw .= "lxc.network.type = veth\n";
1138 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1139 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1140 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1141 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1144 if (my $lxcconf = $conf->{lxc
}) {
1145 foreach my $entry (@$lxcconf) {
1146 my ($k, $v) = @$entry;
1147 $netcount++ if $k eq 'lxc.network.type';
1148 $raw .= "$k = $v\n";
1152 $raw .= "lxc.network.type = empty\n" if !$netcount;
1154 File
::Path
::mkpath
("$dir/rootfs");
1156 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1159 # verify and cleanup nameserver list (replace \0 with ' ')
1160 sub verify_nameserver_list
{
1161 my ($nameserver_list) = @_;
1164 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1165 PVE
::JSONSchema
::pve_verify_ip
($server);
1166 push @list, $server;
1169 return join(' ', @list);
1172 sub verify_searchdomain_list
{
1173 my ($searchdomain_list) = @_;
1176 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1177 # todo: should we add checks for valid dns domains?
1178 push @list, $server;
1181 return join(' ', @list);
1184 sub add_unused_volume
{
1185 my ($config, $volid) = @_;
1188 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1189 my $test = "unused$ind";
1190 if (my $vid = $config->{$test}) {
1191 return if $vid eq $volid; # do not add duplicates
1197 die "To many unused volume - please delete them first.\n" if !$key;
1199 $config->{$key} = $volid;
1204 sub update_pct_config
{
1205 my ($vmid, $conf, $running, $param, $delete) = @_;
1210 my @deleted_volumes;
1214 my $pid = find_lxc_pid
($vmid);
1215 $rootdir = "/proc/$pid/root";
1218 my $hotplug_error = sub {
1220 push @nohotplug, @_;
1227 if (defined($delete)) {
1228 foreach my $opt (@$delete) {
1229 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1230 die "unable to delete required option '$opt'\n";
1231 } elsif ($opt eq 'swap') {
1232 delete $conf->{$opt};
1233 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1234 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1235 delete $conf->{$opt};
1236 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1237 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1238 next if $hotplug_error->($opt);
1239 delete $conf->{$opt};
1240 } elsif ($opt =~ m/^net(\d)$/) {
1241 delete $conf->{$opt};
1244 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1245 } elsif ($opt eq 'protection') {
1246 delete $conf->{$opt};
1247 } elsif ($opt =~ m/^unused(\d+)$/) {
1248 next if $hotplug_error->($opt);
1249 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1250 push @deleted_volumes, $conf->{$opt};
1251 delete $conf->{$opt};
1252 } elsif ($opt =~ m/^mp(\d+)$/) {
1253 next if $hotplug_error->($opt);
1254 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1255 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1256 if ($mountpoint->{type
} eq 'volume') {
1257 add_unused_volume
($conf, $mountpoint->{volume
})
1259 delete $conf->{$opt};
1260 } elsif ($opt eq 'unprivileged') {
1261 die "unable to delete read-only option: '$opt'\n";
1265 write_config
($vmid, $conf) if $running;
1269 # There's no separate swap size to configure, there's memory and "total"
1270 # memory (iow. memory+swap). This means we have to change them together.
1271 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1272 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1273 if (defined($wanted_memory) || defined($wanted_swap)) {
1275 $wanted_memory //= ($conf->{memory
} || 512);
1276 $wanted_swap //= ($conf->{swap
} || 0);
1278 my $total = $wanted_memory + $wanted_swap;
1280 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1281 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1283 $conf->{memory
} = $wanted_memory;
1284 $conf->{swap
} = $wanted_swap;
1286 write_config
($vmid, $conf) if $running;
1289 foreach my $opt (keys %$param) {
1290 my $value = $param->{$opt};
1291 if ($opt eq 'hostname') {
1292 $conf->{$opt} = $value;
1293 } elsif ($opt eq 'onboot') {
1294 $conf->{$opt} = $value ?
1 : 0;
1295 } elsif ($opt eq 'startup') {
1296 $conf->{$opt} = $value;
1297 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1298 next if $hotplug_error->($opt);
1299 $conf->{$opt} = $value;
1300 } elsif ($opt eq 'nameserver') {
1301 next if $hotplug_error->($opt);
1302 my $list = verify_nameserver_list
($value);
1303 $conf->{$opt} = $list;
1304 } elsif ($opt eq 'searchdomain') {
1305 next if $hotplug_error->($opt);
1306 my $list = verify_searchdomain_list
($value);
1307 $conf->{$opt} = $list;
1308 } elsif ($opt eq 'cpulimit') {
1309 next if $hotplug_error->($opt); # FIXME: hotplug
1310 $conf->{$opt} = $value;
1311 } elsif ($opt eq 'cpuunits') {
1312 $conf->{$opt} = $value;
1313 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1314 } elsif ($opt eq 'description') {
1315 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1316 } elsif ($opt =~ m/^net(\d+)$/) {
1318 my $net = parse_lxc_network
($value);
1320 $conf->{$opt} = print_lxc_network
($net);
1322 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1324 } elsif ($opt eq 'protection') {
1325 $conf->{$opt} = $value ?
1 : 0;
1326 } elsif ($opt =~ m/^mp(\d+)$/) {
1327 next if $hotplug_error->($opt);
1328 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1329 $conf->{$opt} = $value;
1331 } elsif ($opt eq 'rootfs') {
1332 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1333 die "implement me: $opt";
1334 } elsif ($opt eq 'unprivileged') {
1335 die "unable to modify read-only option: '$opt'\n";
1337 die "implement me: $opt";
1339 write_config
($vmid, $conf) if $running;
1342 if (@deleted_volumes) {
1343 my $storage_cfg = PVE
::Storage
::config
();
1344 foreach my $volume (@deleted_volumes) {
1345 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1350 my $storage_cfg = PVE
::Storage
::config
();
1351 create_disks
($storage_cfg, $vmid, $conf, $conf);
1354 # This should be the last thing we do here
1355 if ($running && scalar(@nohotplug)) {
1356 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1360 sub has_dev_console
{
1363 return !(defined($conf->{console
}) && !$conf->{console
});
1369 return $conf->{tty
} // $confdesc->{tty
}->{default};
1375 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1378 sub get_console_command
{
1379 my ($vmid, $conf) = @_;
1381 my $cmode = get_cmode
($conf);
1383 if ($cmode eq 'console') {
1384 return ['lxc-console', '-n', $vmid, '-t', 0];
1385 } elsif ($cmode eq 'tty') {
1386 return ['lxc-console', '-n', $vmid];
1387 } elsif ($cmode eq 'shell') {
1388 return ['lxc-attach', '--clear-env', '-n', $vmid];
1390 die "internal error";
1394 sub get_primary_ips
{
1397 # return data from net0
1399 return undef if !defined($conf->{net0
});
1400 my $net = parse_lxc_network
($conf->{net0
});
1402 my $ipv4 = $net->{ip
};
1404 if ($ipv4 =~ /^(dhcp|manual)$/) {
1410 my $ipv6 = $net->{ip6
};
1412 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1419 return ($ipv4, $ipv6);
1422 sub delete_mountpoint_volume
{
1423 my ($storage_cfg, $vmid, $volume) = @_;
1425 return if classify_mountpoint
($volume) ne 'volume';
1427 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1428 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1431 sub destroy_lxc_container
{
1432 my ($storage_cfg, $vmid, $conf) = @_;
1434 foreach_mountpoint
($conf, sub {
1435 my ($ms, $mountpoint) = @_;
1436 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1439 rmdir "/var/lib/lxc/$vmid/rootfs";
1440 unlink "/var/lib/lxc/$vmid/config";
1441 rmdir "/var/lib/lxc/$vmid";
1442 destroy_config
($vmid);
1444 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1445 #PVE::Tools::run_command($cmd);
1448 sub vm_stop_cleanup
{
1449 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1454 my $vollist = get_vm_volumes
($conf);
1455 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1458 warn $@ if $@; # avoid errors - just warn
1461 my $safe_num_ne = sub {
1464 return 0 if !defined($a) && !defined($b);
1465 return 1 if !defined($a);
1466 return 1 if !defined($b);
1471 my $safe_string_ne = sub {
1474 return 0 if !defined($a) && !defined($b);
1475 return 1 if !defined($a);
1476 return 1 if !defined($b);
1482 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1484 if ($newnet->{type
} ne 'veth') {
1485 # for when there are physical interfaces
1486 die "cannot update interface of type $newnet->{type}";
1489 my $veth = "veth${vmid}i${netid}";
1490 my $eth = $newnet->{name
};
1492 if (my $oldnetcfg = $conf->{$opt}) {
1493 my $oldnet = parse_lxc_network
($oldnetcfg);
1495 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1496 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1498 PVE
::Network
::veth_delete
($veth);
1499 delete $conf->{$opt};
1500 write_config
($vmid, $conf);
1502 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1504 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1505 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1506 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1508 if ($oldnet->{bridge
}) {
1509 PVE
::Network
::tap_unplug
($veth);
1510 foreach (qw(bridge tag firewall)) {
1511 delete $oldnet->{$_};
1513 $conf->{$opt} = print_lxc_network
($oldnet);
1514 write_config
($vmid, $conf);
1517 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1518 foreach (qw(bridge tag firewall)) {
1519 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1521 $conf->{$opt} = print_lxc_network
($oldnet);
1522 write_config
($vmid, $conf);
1525 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1528 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1532 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1534 my $veth = "veth${vmid}i${netid}";
1535 my $vethpeer = $veth . "p";
1536 my $eth = $newnet->{name
};
1538 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1539 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1541 # attach peer in container
1542 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1543 PVE
::Tools
::run_command
($cmd);
1545 # link up peer in container
1546 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1547 PVE
::Tools
::run_command
($cmd);
1549 my $done = { type
=> 'veth' };
1550 foreach (qw(bridge tag firewall hwaddr name)) {
1551 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1553 $conf->{$opt} = print_lxc_network
($done);
1555 write_config
($vmid, $conf);
1558 sub update_ipconfig
{
1559 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1561 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1563 my $optdata = parse_lxc_network
($conf->{$opt});
1567 my $cmdargs = shift;
1568 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1570 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1572 my $change_ip_config = sub {
1573 my ($ipversion) = @_;
1575 my $family_opt = "-$ipversion";
1576 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1577 my $gw= "gw$suffix";
1578 my $ip= "ip$suffix";
1580 my $newip = $newnet->{$ip};
1581 my $newgw = $newnet->{$gw};
1582 my $oldip = $optdata->{$ip};
1584 my $change_ip = &$safe_string_ne($oldip, $newip);
1585 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1587 return if !$change_ip && !$change_gw;
1589 # step 1: add new IP, if this fails we cancel
1590 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1591 if ($change_ip && $is_real_ip) {
1592 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1599 # step 2: replace gateway
1600 # If this fails we delete the added IP and cancel.
1601 # If it succeeds we save the config and delete the old IP, ignoring
1602 # errors. The config is then saved.
1603 # Note: 'ip route replace' can add
1607 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1608 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1610 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1614 # the route was not replaced, the old IP is still available
1615 # rollback (delete new IP) and cancel
1617 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1618 warn $@ if $@; # no need to die here
1623 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1624 # if the route was not deleted, the guest might have deleted it manually
1630 # from this point on we save the configuration
1631 # step 3: delete old IP ignoring errors
1632 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1633 # We need to enable promote_secondaries, otherwise our newly added
1634 # address will be removed along with the old one.
1637 if ($ipversion == 4) {
1638 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1639 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1640 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1642 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1644 warn $@ if $@; # no need to die here
1646 if ($ipversion == 4) {
1647 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1651 foreach my $property ($ip, $gw) {
1652 if ($newnet->{$property}) {
1653 $optdata->{$property} = $newnet->{$property};
1655 delete $optdata->{$property};
1658 $conf->{$opt} = print_lxc_network
($optdata);
1659 write_config
($vmid, $conf);
1660 $lxc_setup->setup_network($conf);
1663 &$change_ip_config(4);
1664 &$change_ip_config(6);
1668 # Internal snapshots
1670 # NOTE: Snapshot create/delete involves several non-atomic
1671 # action, and can take a long time.
1672 # So we try to avoid locking the file and use 'lock' variable
1673 # inside the config file instead.
1675 my $snapshot_copy_config = sub {
1676 my ($source, $dest) = @_;
1678 foreach my $k (keys %$source) {
1679 next if $k eq 'snapshots';
1680 next if $k eq 'snapstate';
1681 next if $k eq 'snaptime';
1682 next if $k eq 'vmstate';
1683 next if $k eq 'lock';
1684 next if $k eq 'digest';
1685 next if $k eq 'description';
1687 $dest->{$k} = $source->{$k};
1691 my $snapshot_prepare = sub {
1692 my ($vmid, $snapname, $comment) = @_;
1696 my $updatefn = sub {
1698 my $conf = load_config
($vmid);
1700 die "you can't take a snapshot if it's a template\n"
1701 if is_template
($conf);
1705 $conf->{lock} = 'snapshot';
1707 die "snapshot name '$snapname' already used\n"
1708 if defined($conf->{snapshots
}->{$snapname});
1710 my $storecfg = PVE
::Storage
::config
();
1711 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1713 $snap = $conf->{snapshots
}->{$snapname} = {};
1715 &$snapshot_copy_config($conf, $snap);
1717 $snap->{'snapstate'} = "prepare";
1718 $snap->{'snaptime'} = time();
1719 $snap->{'description'} = $comment if $comment;
1720 $conf->{snapshots
}->{$snapname} = $snap;
1722 write_config
($vmid, $conf);
1725 lock_container
($vmid, 10, $updatefn);
1730 my $snapshot_commit = sub {
1731 my ($vmid, $snapname) = @_;
1733 my $updatefn = sub {
1735 my $conf = load_config
($vmid);
1737 die "missing snapshot lock\n"
1738 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1740 die "snapshot '$snapname' does not exist\n"
1741 if !defined($conf->{snapshots
}->{$snapname});
1743 die "wrong snapshot state\n"
1744 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1745 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1747 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1748 delete $conf->{lock};
1749 $conf->{parent
} = $snapname;
1751 write_config
($vmid, $conf);
1754 lock_container
($vmid, 10 ,$updatefn);
1758 my ($feature, $conf, $storecfg, $snapname) = @_;
1762 foreach_mountpoint
($conf, sub {
1763 my ($ms, $mountpoint) = @_;
1765 return if $err; # skip further test
1767 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1769 # TODO: implement support for mountpoints
1770 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1774 return $err ?
0 : 1;
1777 sub snapshot_create
{
1778 my ($vmid, $snapname, $comment) = @_;
1780 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1782 my $conf = load_config
($vmid);
1784 my $running = check_running
($vmid);
1787 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1788 PVE
::Tools
::run_command
(['/bin/sync']);
1791 my $storecfg = PVE
::Storage
::config
();
1792 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1793 my $volid = $rootinfo->{volume
};
1796 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1799 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1800 &$snapshot_commit($vmid, $snapname);
1803 snapshot_delete
($vmid, $snapname, 1);
1808 sub snapshot_delete
{
1809 my ($vmid, $snapname, $force) = @_;
1815 my $updatefn = sub {
1817 $conf = load_config
($vmid);
1819 die "you can't delete a snapshot if vm is a template\n"
1820 if is_template
($conf);
1822 $snap = $conf->{snapshots
}->{$snapname};
1826 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1828 $snap->{snapstate
} = 'delete';
1830 write_config
($vmid, $conf);
1833 lock_container
($vmid, 10, $updatefn);
1835 my $storecfg = PVE
::Storage
::config
();
1837 my $del_snap = sub {
1841 if ($conf->{parent
} eq $snapname) {
1842 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1843 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1845 delete $conf->{parent
};
1849 delete $conf->{snapshots
}->{$snapname};
1851 write_config
($vmid, $conf);
1854 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1855 my $rootinfo = parse_ct_mountpoint
($rootfs);
1856 my $volid = $rootinfo->{volume
};
1859 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1863 if(!$err || ($err && $force)) {
1864 lock_container
($vmid, 10, $del_snap);
1866 die "Can't delete snapshot: $vmid $snapname $err\n";
1871 sub snapshot_rollback
{
1872 my ($vmid, $snapname) = @_;
1874 my $storecfg = PVE
::Storage
::config
();
1876 my $conf = load_config
($vmid);
1878 die "you can't rollback if vm is a template\n" if is_template
($conf);
1880 my $snap = $conf->{snapshots
}->{$snapname};
1882 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1884 my $rootfs = $snap->{rootfs
};
1885 my $rootinfo = parse_ct_mountpoint
($rootfs);
1886 my $volid = $rootinfo->{volume
};
1888 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1890 my $updatefn = sub {
1892 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1893 if $snap->{snapstate
};
1897 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1899 die "unable to rollback vm $vmid: vm is running\n"
1900 if check_running
($vmid);
1902 $conf->{lock} = 'rollback';
1906 # copy snapshot config to current config
1908 my $tmp_conf = $conf;
1909 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1910 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1911 delete $conf->{snaptime
};
1912 delete $conf->{snapname
};
1913 $conf->{parent
} = $snapname;
1915 write_config
($vmid, $conf);
1918 my $unlockfn = sub {
1919 delete $conf->{lock};
1920 write_config
($vmid, $conf);
1923 lock_container
($vmid, 10, $updatefn);
1925 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1927 lock_container
($vmid, 5, $unlockfn);
1930 sub template_create
{
1931 my ($vmid, $conf) = @_;
1933 my $storecfg = PVE
::Storage
::config
();
1935 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1936 my $volid = $rootinfo->{volume
};
1938 die "Template feature is not available for '$volid'\n"
1939 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1941 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1943 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1944 $rootinfo->{volume
} = $template_volid;
1945 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1947 write_config
($vmid, $conf);
1953 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1956 sub mountpoint_names
{
1959 my @names = ('rootfs');
1961 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1962 push @names, "mp$i";
1965 return $reverse ?
reverse @names : @names;
1968 # The container might have *different* symlinks than the host. realpath/abs_path
1969 # use the actual filesystem to resolve links.
1970 sub sanitize_mountpoint
{
1972 $mp = '/' . $mp; # we always start with a slash
1973 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1974 $mp =~ s
@/\./@@g; # collapse /./
1975 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1976 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1977 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1981 sub foreach_mountpoint_full
{
1982 my ($conf, $reverse, $func) = @_;
1984 foreach my $key (mountpoint_names
($reverse)) {
1985 my $value = $conf->{$key};
1986 next if !defined($value);
1987 my $mountpoint = parse_ct_mountpoint
($value, 1);
1988 next if !defined($mountpoint);
1990 # just to be sure: rootfs is /
1991 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1992 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1994 $path = $mountpoint->{volume
};
1995 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1997 &$func($key, $mountpoint);
2001 sub foreach_mountpoint
{
2002 my ($conf, $func) = @_;
2004 foreach_mountpoint_full
($conf, 0, $func);
2007 sub foreach_mountpoint_reverse
{
2008 my ($conf, $func) = @_;
2010 foreach_mountpoint_full
($conf, 1, $func);
2013 sub check_ct_modify_config_perm
{
2014 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2016 return 1 if $authuser ne 'root@pam';
2018 foreach my $opt (@$key_list) {
2020 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2021 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2022 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2023 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2024 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2025 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2026 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2027 $opt eq 'searchdomain' || $opt eq 'hostname') {
2028 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2030 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2038 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2040 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2041 my $volid_list = get_vm_volumes
($conf);
2043 foreach_mountpoint_reverse
($conf, sub {
2044 my ($ms, $mountpoint) = @_;
2046 my $volid = $mountpoint->{volume
};
2047 my $mount = $mountpoint->{mp
};
2049 return if !$volid || !$mount;
2051 my $mount_path = "$rootdir/$mount";
2052 $mount_path =~ s!/+!/!g;
2054 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2057 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2070 my ($vmid, $storage_cfg, $conf) = @_;
2072 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2073 File
::Path
::make_path
($rootdir);
2075 my $volid_list = get_vm_volumes
($conf);
2076 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2079 foreach_mountpoint
($conf, sub {
2080 my ($ms, $mountpoint) = @_;
2082 my $volid = $mountpoint->{volume
};
2083 my $mount = $mountpoint->{mp
};
2085 return if !$volid || !$mount;
2087 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2088 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2089 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2091 die "unable to mount base volume - internal error" if $isBase;
2093 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2097 warn "mounting container failed - $err";
2098 umount_all
($vmid, $storage_cfg, $conf, 1);
2105 sub mountpoint_mount_path
{
2106 my ($mountpoint, $storage_cfg, $snapname) = @_;
2108 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2111 my $check_mount_path = sub {
2113 $path = File
::Spec-
>canonpath($path);
2114 my $real = Cwd
::realpath
($path);
2115 if ($real ne $path) {
2116 die "mount path modified by symlink: $path != $real";
2125 if ($line =~ m
@^(/dev/loop\d
+):@) {
2129 my $cmd = ['losetup', '--associated', $path];
2130 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2134 # use $rootdir = undef to just return the corresponding mount path
2135 sub mountpoint_mount
{
2136 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2138 my $volid = $mountpoint->{volume
};
2139 my $mount = $mountpoint->{mp
};
2140 my $type = $mountpoint->{type
};
2142 return if !$volid || !$mount;
2146 if (defined($rootdir)) {
2147 $rootdir =~ s!/+$!!;
2148 $mount_path = "$rootdir/$mount";
2149 $mount_path =~ s!/+!/!g;
2150 &$check_mount_path($mount_path);
2151 File
::Path
::mkpath
($mount_path);
2154 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2156 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2160 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2161 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2163 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2164 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2166 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2168 if ($format eq 'subvol') {
2171 if ($scfg->{type
} ne 'zfspool') {
2172 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2175 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2177 return wantarray ?
($path, 0) : $path;
2178 } elsif ($format eq 'raw' || $format eq 'iso') {
2179 my $use_loopdev = 0;
2181 if ($scfg->{path
}) {
2182 push @extra_opts, '-o', 'loop';
2184 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2187 die "unsupported storage type '$scfg->{type}'\n";
2190 if ($format eq 'iso') {
2191 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2192 } elsif ($isBase || defined($snapname)) {
2193 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2195 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2198 return wantarray ?
($path, $use_loopdev) : $path;
2200 die "unsupported image format '$format'\n";
2202 } elsif ($type eq 'device') {
2203 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2204 return wantarray ?
($volid, 0) : $volid;
2205 } elsif ($type eq 'bind' && -d
$volid) {
2206 &$check_mount_path($volid);
2207 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2208 return wantarray ?
($volid, 0) : $volid;
2211 die "unsupported storage";
2214 sub get_vm_volumes
{
2215 my ($conf, $excludes) = @_;
2219 foreach_mountpoint
($conf, sub {
2220 my ($ms, $mountpoint) = @_;
2222 return if $excludes && $ms eq $excludes;
2224 my $volid = $mountpoint->{volume
};
2226 return if !$volid || $mountpoint->{type
} ne 'volume';
2228 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2231 push @$vollist, $volid;
2238 my ($dev, $rootuid, $rootgid) = @_;
2240 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2241 '-E', "root_owner=$rootuid:$rootgid",
2246 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2248 if ($volid =~ m!^/dev/.+!) {
2253 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2255 die "cannot format volume '$volid' with no storage\n" if !$storage;
2257 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2259 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2261 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2262 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2264 die "cannot format volume '$volid' (format == $format)\n"
2265 if $format ne 'raw';
2267 mkfs
($path, $rootuid, $rootgid);
2271 my ($storecfg, $vollist) = @_;
2273 foreach my $volid (@$vollist) {
2274 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2280 my ($storecfg, $vmid, $settings, $conf) = @_;
2285 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2286 my $chown_vollist = [];
2288 foreach_mountpoint
($settings, sub {
2289 my ($ms, $mountpoint) = @_;
2291 my $volid = $mountpoint->{volume
};
2292 my $mp = $mountpoint->{mp
};
2294 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2296 return if !$storage;
2298 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2299 my ($storeid, $size_gb) = ($1, $2);
2301 my $size_kb = int(${size_gb
}*1024) * 1024;
2303 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2304 # fixme: use better naming ct-$vmid-disk-X.raw?
2306 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2308 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2310 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2312 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2314 push @$chown_vollist, $volid;
2316 } elsif ($scfg->{type
} eq 'zfspool') {
2318 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2320 push @$chown_vollist, $volid;
2321 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2323 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2324 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2326 } elsif ($scfg->{type
} eq 'rbd') {
2328 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2329 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2330 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2332 die "unable to create containers on storage type '$scfg->{type}'\n";
2334 push @$vollist, $volid;
2335 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2336 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2338 # use specified/existing volid
2342 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2343 foreach my $volid (@$chown_vollist) {
2344 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2345 chown($rootuid, $rootgid, $path);
2347 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2349 # free allocated images on error
2351 destroy_disks
($storecfg, $vollist);
2357 # bash completion helper
2359 sub complete_os_templates
{
2360 my ($cmdname, $pname, $cvalue) = @_;
2362 my $cfg = PVE
::Storage
::config
();
2366 if ($cvalue =~ m/^([^:]+):/) {
2370 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2371 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2374 foreach my $id (keys %$data) {
2375 foreach my $item (@{$data->{$id}}) {
2376 push @$res, $item->{volid
} if defined($item->{volid
});
2383 my $complete_ctid_full = sub {
2386 my $idlist = vmstatus
();
2388 my $active_hash = list_active_containers
();
2392 foreach my $id (keys %$idlist) {
2393 my $d = $idlist->{$id};
2394 if (defined($running)) {
2395 next if $d->{template
};
2396 next if $running && !$active_hash->{$id};
2397 next if !$running && $active_hash->{$id};
2406 return &$complete_ctid_full();
2409 sub complete_ctid_stopped
{
2410 return &$complete_ctid_full(0);
2413 sub complete_ctid_running
{
2414 return &$complete_ctid_full(1);
2424 my $lxc = $conf->{lxc
};
2425 foreach my $entry (@$lxc) {
2426 my ($key, $value) = @$entry;
2427 next if $key ne 'lxc.id_map';
2428 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2429 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2430 push @$id_map, [$type, $ct, $host, $length];
2432 $rootuid = $host if $type eq 'u';
2433 $rootgid = $host if $type eq 'g';
2436 die "failed to parse id_map: $value\n";
2440 if (!@$id_map && $conf->{unprivileged
}) {
2441 # Should we read them from /etc/subuid?
2442 $id_map = [ ['u', '0', '100000', '65536'],
2443 ['g', '0', '100000', '65536'] ];
2444 $rootuid = $rootgid = 100000;
2447 return ($id_map, $rootuid, $rootgid);
2450 sub userns_command
{
2453 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];