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', 'fedora', 'opensuse', '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 | fedora | opensuse | 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"
1081 die "implement me (ostype $ostype)";
1084 $raw .= "lxc.monitor.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";
1131 foreach my $k (keys %$conf) {
1132 next if $k !~ m/^net(\d+)$/;
1134 my $d = parse_lxc_network
($conf->{$k});
1136 $raw .= "lxc.network.type = veth\n";
1137 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1138 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1139 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1140 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1143 if (my $lxcconf = $conf->{lxc
}) {
1144 foreach my $entry (@$lxcconf) {
1145 my ($k, $v) = @$entry;
1146 $netcount++ if $k eq 'lxc.network.type';
1147 $raw .= "$k = $v\n";
1151 $raw .= "lxc.network.type = empty\n" if !$netcount;
1153 File
::Path
::mkpath
("$dir/rootfs");
1155 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1158 # verify and cleanup nameserver list (replace \0 with ' ')
1159 sub verify_nameserver_list
{
1160 my ($nameserver_list) = @_;
1163 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1164 PVE
::JSONSchema
::pve_verify_ip
($server);
1165 push @list, $server;
1168 return join(' ', @list);
1171 sub verify_searchdomain_list
{
1172 my ($searchdomain_list) = @_;
1175 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1176 # todo: should we add checks for valid dns domains?
1177 push @list, $server;
1180 return join(' ', @list);
1183 sub add_unused_volume
{
1184 my ($config, $volid) = @_;
1187 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1188 my $test = "unused$ind";
1189 if (my $vid = $config->{$test}) {
1190 return if $vid eq $volid; # do not add duplicates
1196 die "To many unused volume - please delete them first.\n" if !$key;
1198 $config->{$key} = $volid;
1203 sub update_pct_config
{
1204 my ($vmid, $conf, $running, $param, $delete) = @_;
1209 my @deleted_volumes;
1213 my $pid = find_lxc_pid
($vmid);
1214 $rootdir = "/proc/$pid/root";
1217 my $hotplug_error = sub {
1219 push @nohotplug, @_;
1226 if (defined($delete)) {
1227 foreach my $opt (@$delete) {
1228 if (!exists($conf->{$opt})) {
1229 warn "no such option: $opt\n";
1233 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1234 die "unable to delete required option '$opt'\n";
1235 } elsif ($opt eq 'swap') {
1236 delete $conf->{$opt};
1237 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1238 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1239 delete $conf->{$opt};
1240 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1241 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1242 next if $hotplug_error->($opt);
1243 delete $conf->{$opt};
1244 } elsif ($opt =~ m/^net(\d)$/) {
1245 delete $conf->{$opt};
1248 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1249 } elsif ($opt eq 'protection') {
1250 delete $conf->{$opt};
1251 } elsif ($opt =~ m/^unused(\d+)$/) {
1252 next if $hotplug_error->($opt);
1253 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1254 push @deleted_volumes, $conf->{$opt};
1255 delete $conf->{$opt};
1256 } elsif ($opt =~ m/^mp(\d+)$/) {
1257 next if $hotplug_error->($opt);
1258 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1259 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1260 if ($mountpoint->{type
} eq 'volume') {
1261 add_unused_volume
($conf, $mountpoint->{volume
})
1263 delete $conf->{$opt};
1264 } elsif ($opt eq 'unprivileged') {
1265 die "unable to delete read-only option: '$opt'\n";
1267 die "implement me (delete: $opt)"
1269 write_config
($vmid, $conf) if $running;
1273 # There's no separate swap size to configure, there's memory and "total"
1274 # memory (iow. memory+swap). This means we have to change them together.
1275 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1276 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1277 if (defined($wanted_memory) || defined($wanted_swap)) {
1279 $wanted_memory //= ($conf->{memory
} || 512);
1280 $wanted_swap //= ($conf->{swap
} || 0);
1282 my $total = $wanted_memory + $wanted_swap;
1284 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1285 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1287 $conf->{memory
} = $wanted_memory;
1288 $conf->{swap
} = $wanted_swap;
1290 write_config
($vmid, $conf) if $running;
1293 foreach my $opt (keys %$param) {
1294 my $value = $param->{$opt};
1295 if ($opt eq 'hostname') {
1296 $conf->{$opt} = $value;
1297 } elsif ($opt eq 'onboot') {
1298 $conf->{$opt} = $value ?
1 : 0;
1299 } elsif ($opt eq 'startup') {
1300 $conf->{$opt} = $value;
1301 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1302 next if $hotplug_error->($opt);
1303 $conf->{$opt} = $value;
1304 } elsif ($opt eq 'nameserver') {
1305 next if $hotplug_error->($opt);
1306 my $list = verify_nameserver_list
($value);
1307 $conf->{$opt} = $list;
1308 } elsif ($opt eq 'searchdomain') {
1309 next if $hotplug_error->($opt);
1310 my $list = verify_searchdomain_list
($value);
1311 $conf->{$opt} = $list;
1312 } elsif ($opt eq 'cpulimit') {
1313 next if $hotplug_error->($opt); # FIXME: hotplug
1314 $conf->{$opt} = $value;
1315 } elsif ($opt eq 'cpuunits') {
1316 $conf->{$opt} = $value;
1317 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1318 } elsif ($opt eq 'description') {
1319 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1320 } elsif ($opt =~ m/^net(\d+)$/) {
1322 my $net = parse_lxc_network
($value);
1324 $conf->{$opt} = print_lxc_network
($net);
1326 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1328 } elsif ($opt eq 'protection') {
1329 $conf->{$opt} = $value ?
1 : 0;
1330 } elsif ($opt =~ m/^mp(\d+)$/) {
1331 next if $hotplug_error->($opt);
1332 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1333 $conf->{$opt} = $value;
1335 } elsif ($opt eq 'rootfs') {
1336 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1337 die "implement me: $opt";
1338 } elsif ($opt eq 'unprivileged') {
1339 die "unable to modify read-only option: '$opt'\n";
1341 die "implement me: $opt";
1343 write_config
($vmid, $conf) if $running;
1346 if (@deleted_volumes) {
1347 my $storage_cfg = PVE
::Storage
::config
();
1348 foreach my $volume (@deleted_volumes) {
1349 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1354 my $storage_cfg = PVE
::Storage
::config
();
1355 create_disks
($storage_cfg, $vmid, $conf, $conf);
1358 # This should be the last thing we do here
1359 if ($running && scalar(@nohotplug)) {
1360 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1364 sub has_dev_console
{
1367 return !(defined($conf->{console
}) && !$conf->{console
});
1373 return $conf->{tty
} // $confdesc->{tty
}->{default};
1379 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1382 sub get_console_command
{
1383 my ($vmid, $conf) = @_;
1385 my $cmode = get_cmode
($conf);
1387 if ($cmode eq 'console') {
1388 return ['lxc-console', '-n', $vmid, '-t', 0];
1389 } elsif ($cmode eq 'tty') {
1390 return ['lxc-console', '-n', $vmid];
1391 } elsif ($cmode eq 'shell') {
1392 return ['lxc-attach', '--clear-env', '-n', $vmid];
1394 die "internal error";
1398 sub get_primary_ips
{
1401 # return data from net0
1403 return undef if !defined($conf->{net0
});
1404 my $net = parse_lxc_network
($conf->{net0
});
1406 my $ipv4 = $net->{ip
};
1408 if ($ipv4 =~ /^(dhcp|manual)$/) {
1414 my $ipv6 = $net->{ip6
};
1416 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1423 return ($ipv4, $ipv6);
1426 sub delete_mountpoint_volume
{
1427 my ($storage_cfg, $vmid, $volume) = @_;
1429 return if classify_mountpoint
($volume) ne 'volume';
1431 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1432 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1435 sub destroy_lxc_container
{
1436 my ($storage_cfg, $vmid, $conf) = @_;
1438 foreach_mountpoint
($conf, sub {
1439 my ($ms, $mountpoint) = @_;
1440 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1443 rmdir "/var/lib/lxc/$vmid/rootfs";
1444 unlink "/var/lib/lxc/$vmid/config";
1445 rmdir "/var/lib/lxc/$vmid";
1446 destroy_config
($vmid);
1448 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1449 #PVE::Tools::run_command($cmd);
1452 sub vm_stop_cleanup
{
1453 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1458 my $vollist = get_vm_volumes
($conf);
1459 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1462 warn $@ if $@; # avoid errors - just warn
1465 my $safe_num_ne = sub {
1468 return 0 if !defined($a) && !defined($b);
1469 return 1 if !defined($a);
1470 return 1 if !defined($b);
1475 my $safe_string_ne = sub {
1478 return 0 if !defined($a) && !defined($b);
1479 return 1 if !defined($a);
1480 return 1 if !defined($b);
1486 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1488 if ($newnet->{type
} ne 'veth') {
1489 # for when there are physical interfaces
1490 die "cannot update interface of type $newnet->{type}";
1493 my $veth = "veth${vmid}i${netid}";
1494 my $eth = $newnet->{name
};
1496 if (my $oldnetcfg = $conf->{$opt}) {
1497 my $oldnet = parse_lxc_network
($oldnetcfg);
1499 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1500 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1502 PVE
::Network
::veth_delete
($veth);
1503 delete $conf->{$opt};
1504 write_config
($vmid, $conf);
1506 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1508 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1509 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1510 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1512 if ($oldnet->{bridge
}) {
1513 PVE
::Network
::tap_unplug
($veth);
1514 foreach (qw(bridge tag firewall)) {
1515 delete $oldnet->{$_};
1517 $conf->{$opt} = print_lxc_network
($oldnet);
1518 write_config
($vmid, $conf);
1521 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1522 foreach (qw(bridge tag firewall)) {
1523 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1525 $conf->{$opt} = print_lxc_network
($oldnet);
1526 write_config
($vmid, $conf);
1529 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1532 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1536 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1538 my $veth = "veth${vmid}i${netid}";
1539 my $vethpeer = $veth . "p";
1540 my $eth = $newnet->{name
};
1542 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1543 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1545 # attach peer in container
1546 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1547 PVE
::Tools
::run_command
($cmd);
1549 # link up peer in container
1550 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1551 PVE
::Tools
::run_command
($cmd);
1553 my $done = { type
=> 'veth' };
1554 foreach (qw(bridge tag firewall hwaddr name)) {
1555 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1557 $conf->{$opt} = print_lxc_network
($done);
1559 write_config
($vmid, $conf);
1562 sub update_ipconfig
{
1563 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1565 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1567 my $optdata = parse_lxc_network
($conf->{$opt});
1571 my $cmdargs = shift;
1572 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1574 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1576 my $change_ip_config = sub {
1577 my ($ipversion) = @_;
1579 my $family_opt = "-$ipversion";
1580 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1581 my $gw= "gw$suffix";
1582 my $ip= "ip$suffix";
1584 my $newip = $newnet->{$ip};
1585 my $newgw = $newnet->{$gw};
1586 my $oldip = $optdata->{$ip};
1588 my $change_ip = &$safe_string_ne($oldip, $newip);
1589 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1591 return if !$change_ip && !$change_gw;
1593 # step 1: add new IP, if this fails we cancel
1594 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1595 if ($change_ip && $is_real_ip) {
1596 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1603 # step 2: replace gateway
1604 # If this fails we delete the added IP and cancel.
1605 # If it succeeds we save the config and delete the old IP, ignoring
1606 # errors. The config is then saved.
1607 # Note: 'ip route replace' can add
1611 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1612 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1614 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1618 # the route was not replaced, the old IP is still available
1619 # rollback (delete new IP) and cancel
1621 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1622 warn $@ if $@; # no need to die here
1627 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1628 # if the route was not deleted, the guest might have deleted it manually
1634 # from this point on we save the configuration
1635 # step 3: delete old IP ignoring errors
1636 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1637 # We need to enable promote_secondaries, otherwise our newly added
1638 # address will be removed along with the old one.
1641 if ($ipversion == 4) {
1642 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1643 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1644 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1646 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1648 warn $@ if $@; # no need to die here
1650 if ($ipversion == 4) {
1651 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1655 foreach my $property ($ip, $gw) {
1656 if ($newnet->{$property}) {
1657 $optdata->{$property} = $newnet->{$property};
1659 delete $optdata->{$property};
1662 $conf->{$opt} = print_lxc_network
($optdata);
1663 write_config
($vmid, $conf);
1664 $lxc_setup->setup_network($conf);
1667 &$change_ip_config(4);
1668 &$change_ip_config(6);
1672 # Internal snapshots
1674 # NOTE: Snapshot create/delete involves several non-atomic
1675 # action, and can take a long time.
1676 # So we try to avoid locking the file and use 'lock' variable
1677 # inside the config file instead.
1679 my $snapshot_copy_config = sub {
1680 my ($source, $dest) = @_;
1682 foreach my $k (keys %$source) {
1683 next if $k eq 'snapshots';
1684 next if $k eq 'snapstate';
1685 next if $k eq 'snaptime';
1686 next if $k eq 'vmstate';
1687 next if $k eq 'lock';
1688 next if $k eq 'digest';
1689 next if $k eq 'description';
1691 $dest->{$k} = $source->{$k};
1695 my $snapshot_prepare = sub {
1696 my ($vmid, $snapname, $comment) = @_;
1700 my $updatefn = sub {
1702 my $conf = load_config
($vmid);
1704 die "you can't take a snapshot if it's a template\n"
1705 if is_template
($conf);
1709 $conf->{lock} = 'snapshot';
1711 die "snapshot name '$snapname' already used\n"
1712 if defined($conf->{snapshots
}->{$snapname});
1714 my $storecfg = PVE
::Storage
::config
();
1715 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1717 $snap = $conf->{snapshots
}->{$snapname} = {};
1719 &$snapshot_copy_config($conf, $snap);
1721 $snap->{'snapstate'} = "prepare";
1722 $snap->{'snaptime'} = time();
1723 $snap->{'description'} = $comment if $comment;
1724 $conf->{snapshots
}->{$snapname} = $snap;
1726 write_config
($vmid, $conf);
1729 lock_container
($vmid, 10, $updatefn);
1734 my $snapshot_commit = sub {
1735 my ($vmid, $snapname) = @_;
1737 my $updatefn = sub {
1739 my $conf = load_config
($vmid);
1741 die "missing snapshot lock\n"
1742 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1744 die "snapshot '$snapname' does not exist\n"
1745 if !defined($conf->{snapshots
}->{$snapname});
1747 die "wrong snapshot state\n"
1748 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1749 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1751 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1752 delete $conf->{lock};
1753 $conf->{parent
} = $snapname;
1755 write_config
($vmid, $conf);
1758 lock_container
($vmid, 10 ,$updatefn);
1762 my ($feature, $conf, $storecfg, $snapname) = @_;
1766 foreach_mountpoint
($conf, sub {
1767 my ($ms, $mountpoint) = @_;
1769 return if $err; # skip further test
1771 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1773 # TODO: implement support for mountpoints
1774 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1778 return $err ?
0 : 1;
1781 sub snapshot_create
{
1782 my ($vmid, $snapname, $comment) = @_;
1784 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1786 my $conf = load_config
($vmid);
1788 my $running = check_running
($vmid);
1791 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1792 PVE
::Tools
::run_command
(['/bin/sync']);
1795 my $storecfg = PVE
::Storage
::config
();
1796 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1797 my $volid = $rootinfo->{volume
};
1800 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1803 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1804 &$snapshot_commit($vmid, $snapname);
1807 snapshot_delete
($vmid, $snapname, 1);
1812 sub snapshot_delete
{
1813 my ($vmid, $snapname, $force) = @_;
1819 my $updatefn = sub {
1821 $conf = load_config
($vmid);
1823 die "you can't delete a snapshot if vm is a template\n"
1824 if is_template
($conf);
1826 $snap = $conf->{snapshots
}->{$snapname};
1830 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1832 $snap->{snapstate
} = 'delete';
1834 write_config
($vmid, $conf);
1837 lock_container
($vmid, 10, $updatefn);
1839 my $storecfg = PVE
::Storage
::config
();
1841 my $del_snap = sub {
1845 if ($conf->{parent
} eq $snapname) {
1846 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1847 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1849 delete $conf->{parent
};
1853 delete $conf->{snapshots
}->{$snapname};
1855 write_config
($vmid, $conf);
1858 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1859 my $rootinfo = parse_ct_mountpoint
($rootfs);
1860 my $volid = $rootinfo->{volume
};
1863 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1867 if(!$err || ($err && $force)) {
1868 lock_container
($vmid, 10, $del_snap);
1870 die "Can't delete snapshot: $vmid $snapname $err\n";
1875 sub snapshot_rollback
{
1876 my ($vmid, $snapname) = @_;
1878 my $storecfg = PVE
::Storage
::config
();
1880 my $conf = load_config
($vmid);
1882 die "you can't rollback if vm is a template\n" if is_template
($conf);
1884 my $snap = $conf->{snapshots
}->{$snapname};
1886 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1888 my $rootfs = $snap->{rootfs
};
1889 my $rootinfo = parse_ct_mountpoint
($rootfs);
1890 my $volid = $rootinfo->{volume
};
1892 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1894 my $updatefn = sub {
1896 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1897 if $snap->{snapstate
};
1901 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1903 die "unable to rollback vm $vmid: vm is running\n"
1904 if check_running
($vmid);
1906 $conf->{lock} = 'rollback';
1910 # copy snapshot config to current config
1912 my $tmp_conf = $conf;
1913 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1914 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1915 delete $conf->{snaptime
};
1916 delete $conf->{snapname
};
1917 $conf->{parent
} = $snapname;
1919 write_config
($vmid, $conf);
1922 my $unlockfn = sub {
1923 delete $conf->{lock};
1924 write_config
($vmid, $conf);
1927 lock_container
($vmid, 10, $updatefn);
1929 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1931 lock_container
($vmid, 5, $unlockfn);
1934 sub template_create
{
1935 my ($vmid, $conf) = @_;
1937 my $storecfg = PVE
::Storage
::config
();
1939 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1940 my $volid = $rootinfo->{volume
};
1942 die "Template feature is not available for '$volid'\n"
1943 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1945 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1947 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1948 $rootinfo->{volume
} = $template_volid;
1949 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1951 write_config
($vmid, $conf);
1957 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1960 sub mountpoint_names
{
1963 my @names = ('rootfs');
1965 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1966 push @names, "mp$i";
1969 return $reverse ?
reverse @names : @names;
1972 # The container might have *different* symlinks than the host. realpath/abs_path
1973 # use the actual filesystem to resolve links.
1974 sub sanitize_mountpoint
{
1976 $mp = '/' . $mp; # we always start with a slash
1977 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1978 $mp =~ s
@/\./@@g; # collapse /./
1979 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1980 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1981 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1985 sub foreach_mountpoint_full
{
1986 my ($conf, $reverse, $func) = @_;
1988 foreach my $key (mountpoint_names
($reverse)) {
1989 my $value = $conf->{$key};
1990 next if !defined($value);
1991 my $mountpoint = parse_ct_mountpoint
($value, 1);
1992 next if !defined($mountpoint);
1994 # just to be sure: rootfs is /
1995 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1996 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1998 $path = $mountpoint->{volume
};
1999 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2001 &$func($key, $mountpoint);
2005 sub foreach_mountpoint
{
2006 my ($conf, $func) = @_;
2008 foreach_mountpoint_full
($conf, 0, $func);
2011 sub foreach_mountpoint_reverse
{
2012 my ($conf, $func) = @_;
2014 foreach_mountpoint_full
($conf, 1, $func);
2017 sub check_ct_modify_config_perm
{
2018 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2020 return 1 if $authuser ne 'root@pam';
2022 foreach my $opt (@$key_list) {
2024 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2025 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2026 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2027 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2028 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2029 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2030 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2031 $opt eq 'searchdomain' || $opt eq 'hostname') {
2032 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2034 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2042 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2044 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2045 my $volid_list = get_vm_volumes
($conf);
2047 foreach_mountpoint_reverse
($conf, sub {
2048 my ($ms, $mountpoint) = @_;
2050 my $volid = $mountpoint->{volume
};
2051 my $mount = $mountpoint->{mp
};
2053 return if !$volid || !$mount;
2055 my $mount_path = "$rootdir/$mount";
2056 $mount_path =~ s!/+!/!g;
2058 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2061 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2074 my ($vmid, $storage_cfg, $conf) = @_;
2076 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2077 File
::Path
::make_path
($rootdir);
2079 my $volid_list = get_vm_volumes
($conf);
2080 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2083 foreach_mountpoint
($conf, sub {
2084 my ($ms, $mountpoint) = @_;
2086 my $volid = $mountpoint->{volume
};
2087 my $mount = $mountpoint->{mp
};
2089 return if !$volid || !$mount;
2091 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2092 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2093 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2095 die "unable to mount base volume - internal error" if $isBase;
2097 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2101 warn "mounting container failed - $err";
2102 umount_all
($vmid, $storage_cfg, $conf, 1);
2109 sub mountpoint_mount_path
{
2110 my ($mountpoint, $storage_cfg, $snapname) = @_;
2112 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2115 my $check_mount_path = sub {
2117 $path = File
::Spec-
>canonpath($path);
2118 my $real = Cwd
::realpath
($path);
2119 if ($real ne $path) {
2120 die "mount path modified by symlink: $path != $real";
2129 if ($line =~ m
@^(/dev/loop\d
+):@) {
2133 my $cmd = ['losetup', '--associated', $path];
2134 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2138 # use $rootdir = undef to just return the corresponding mount path
2139 sub mountpoint_mount
{
2140 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2142 my $volid = $mountpoint->{volume
};
2143 my $mount = $mountpoint->{mp
};
2144 my $type = $mountpoint->{type
};
2146 return if !$volid || !$mount;
2150 if (defined($rootdir)) {
2151 $rootdir =~ s!/+$!!;
2152 $mount_path = "$rootdir/$mount";
2153 $mount_path =~ s!/+!/!g;
2154 &$check_mount_path($mount_path);
2155 File
::Path
::mkpath
($mount_path);
2158 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2160 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2164 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2165 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2167 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2168 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2170 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2172 if ($format eq 'subvol') {
2175 if ($scfg->{type
} eq 'zfspool') {
2176 my $path_arg = $path;
2177 $path_arg =~ s!^/+!!;
2178 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2180 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2183 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2186 return wantarray ?
($path, 0) : $path;
2187 } elsif ($format eq 'raw' || $format eq 'iso') {
2188 my $use_loopdev = 0;
2190 if ($scfg->{path
}) {
2191 push @extra_opts, '-o', 'loop';
2193 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2196 die "unsupported storage type '$scfg->{type}'\n";
2199 if ($format eq 'iso') {
2200 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2201 } elsif ($isBase || defined($snapname)) {
2202 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2204 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2207 return wantarray ?
($path, $use_loopdev) : $path;
2209 die "unsupported image format '$format'\n";
2211 } elsif ($type eq 'device') {
2212 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2213 return wantarray ?
($volid, 0) : $volid;
2214 } elsif ($type eq 'bind' && -d
$volid) {
2215 &$check_mount_path($volid);
2216 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2217 return wantarray ?
($volid, 0) : $volid;
2220 die "unsupported storage";
2223 sub get_vm_volumes
{
2224 my ($conf, $excludes) = @_;
2228 foreach_mountpoint
($conf, sub {
2229 my ($ms, $mountpoint) = @_;
2231 return if $excludes && $ms eq $excludes;
2233 my $volid = $mountpoint->{volume
};
2235 return if !$volid || $mountpoint->{type
} ne 'volume';
2237 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2240 push @$vollist, $volid;
2247 my ($dev, $rootuid, $rootgid) = @_;
2249 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2250 '-E', "root_owner=$rootuid:$rootgid",
2255 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2257 if ($volid =~ m!^/dev/.+!) {
2262 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2264 die "cannot format volume '$volid' with no storage\n" if !$storage;
2266 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2268 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2270 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2271 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2273 die "cannot format volume '$volid' (format == $format)\n"
2274 if $format ne 'raw';
2276 mkfs
($path, $rootuid, $rootgid);
2280 my ($storecfg, $vollist) = @_;
2282 foreach my $volid (@$vollist) {
2283 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2289 my ($storecfg, $vmid, $settings, $conf) = @_;
2294 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2295 my $chown_vollist = [];
2297 foreach_mountpoint
($settings, sub {
2298 my ($ms, $mountpoint) = @_;
2300 my $volid = $mountpoint->{volume
};
2301 my $mp = $mountpoint->{mp
};
2303 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2305 return if !$storage;
2307 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2308 my ($storeid, $size_gb) = ($1, $2);
2310 my $size_kb = int(${size_gb
}*1024) * 1024;
2312 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2313 # fixme: use better naming ct-$vmid-disk-X.raw?
2315 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2317 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2319 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2321 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2323 push @$chown_vollist, $volid;
2325 } elsif ($scfg->{type
} eq 'zfspool') {
2327 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2329 push @$chown_vollist, $volid;
2330 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2332 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2333 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2335 } elsif ($scfg->{type
} eq 'rbd') {
2337 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2338 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2339 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2341 die "unable to create containers on storage type '$scfg->{type}'\n";
2343 push @$vollist, $volid;
2344 $mountpoint->{volume
} = $volid;
2345 $mountpoint->{size
} = $size_kb * 1024;
2346 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2348 # use specified/existing volid
2352 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2353 foreach my $volid (@$chown_vollist) {
2354 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2355 chown($rootuid, $rootgid, $path);
2357 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2359 # free allocated images on error
2361 destroy_disks
($storecfg, $vollist);
2367 # bash completion helper
2369 sub complete_os_templates
{
2370 my ($cmdname, $pname, $cvalue) = @_;
2372 my $cfg = PVE
::Storage
::config
();
2376 if ($cvalue =~ m/^([^:]+):/) {
2380 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2381 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2384 foreach my $id (keys %$data) {
2385 foreach my $item (@{$data->{$id}}) {
2386 push @$res, $item->{volid
} if defined($item->{volid
});
2393 my $complete_ctid_full = sub {
2396 my $idlist = vmstatus
();
2398 my $active_hash = list_active_containers
();
2402 foreach my $id (keys %$idlist) {
2403 my $d = $idlist->{$id};
2404 if (defined($running)) {
2405 next if $d->{template
};
2406 next if $running && !$active_hash->{$id};
2407 next if !$running && $active_hash->{$id};
2416 return &$complete_ctid_full();
2419 sub complete_ctid_stopped
{
2420 return &$complete_ctid_full(0);
2423 sub complete_ctid_running
{
2424 return &$complete_ctid_full(1);
2434 my $lxc = $conf->{lxc
};
2435 foreach my $entry (@$lxc) {
2436 my ($key, $value) = @$entry;
2437 next if $key ne 'lxc.id_map';
2438 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2439 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2440 push @$id_map, [$type, $ct, $host, $length];
2442 $rootuid = $host if $type eq 'u';
2443 $rootgid = $host if $type eq 'g';
2446 die "failed to parse id_map: $value\n";
2450 if (!@$id_map && $conf->{unprivileged
}) {
2451 # Should we read them from /etc/subuid?
2452 $id_map = [ ['u', '0', '100000', '65536'],
2453 ['g', '0', '100000', '65536'] ];
2454 $rootuid = $rootgid = 100000;
2457 return ($id_map, $rootuid, $rootgid);
2460 sub userns_command
{
2463 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];