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";
1112 my $lxcswap = int(($memory + $swap)*1024*1024);
1113 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1115 if (my $cpulimit = $conf->{cpulimit
}) {
1116 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1117 my $value = int(100000*$cpulimit);
1118 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1121 my $shares = $conf->{cpuunits
} || 1024;
1122 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1124 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1125 $mountpoint->{mp
} = '/';
1127 $raw .= "lxc.rootfs = $dir/rootfs\n";
1130 foreach my $k (keys %$conf) {
1131 next if $k !~ m/^net(\d+)$/;
1133 my $d = parse_lxc_network
($conf->{$k});
1135 $raw .= "lxc.network.type = veth\n";
1136 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1137 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1138 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1139 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1142 if (my $lxcconf = $conf->{lxc
}) {
1143 foreach my $entry (@$lxcconf) {
1144 my ($k, $v) = @$entry;
1145 $netcount++ if $k eq 'lxc.network.type';
1146 $raw .= "$k = $v\n";
1150 $raw .= "lxc.network.type = empty\n" if !$netcount;
1152 File
::Path
::mkpath
("$dir/rootfs");
1154 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1157 # verify and cleanup nameserver list (replace \0 with ' ')
1158 sub verify_nameserver_list
{
1159 my ($nameserver_list) = @_;
1162 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1163 PVE
::JSONSchema
::pve_verify_ip
($server);
1164 push @list, $server;
1167 return join(' ', @list);
1170 sub verify_searchdomain_list
{
1171 my ($searchdomain_list) = @_;
1174 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1175 # todo: should we add checks for valid dns domains?
1176 push @list, $server;
1179 return join(' ', @list);
1182 sub add_unused_volume
{
1183 my ($config, $volid) = @_;
1186 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1187 my $test = "unused$ind";
1188 if (my $vid = $config->{$test}) {
1189 return if $vid eq $volid; # do not add duplicates
1195 die "To many unused volume - please delete them first.\n" if !$key;
1197 $config->{$key} = $volid;
1202 sub update_pct_config
{
1203 my ($vmid, $conf, $running, $param, $delete) = @_;
1208 my @deleted_volumes;
1212 my $pid = find_lxc_pid
($vmid);
1213 $rootdir = "/proc/$pid/root";
1216 my $hotplug_error = sub {
1218 push @nohotplug, @_;
1225 if (defined($delete)) {
1226 foreach my $opt (@$delete) {
1227 if (!exists($conf->{$opt})) {
1228 warn "no such option: $opt\n";
1232 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1233 die "unable to delete required option '$opt'\n";
1234 } elsif ($opt eq 'swap') {
1235 delete $conf->{$opt};
1236 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1237 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1238 delete $conf->{$opt};
1239 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1240 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1241 next if $hotplug_error->($opt);
1242 delete $conf->{$opt};
1243 } elsif ($opt =~ m/^net(\d)$/) {
1244 delete $conf->{$opt};
1247 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1248 } elsif ($opt eq 'protection') {
1249 delete $conf->{$opt};
1250 } elsif ($opt =~ m/^unused(\d+)$/) {
1251 next if $hotplug_error->($opt);
1252 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1253 push @deleted_volumes, $conf->{$opt};
1254 delete $conf->{$opt};
1255 } elsif ($opt =~ m/^mp(\d+)$/) {
1256 next if $hotplug_error->($opt);
1257 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1258 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1259 if ($mountpoint->{type
} eq 'volume') {
1260 add_unused_volume
($conf, $mountpoint->{volume
})
1262 delete $conf->{$opt};
1263 } elsif ($opt eq 'unprivileged') {
1264 die "unable to delete read-only option: '$opt'\n";
1266 die "implement me (delete: $opt)"
1268 write_config
($vmid, $conf) if $running;
1272 # There's no separate swap size to configure, there's memory and "total"
1273 # memory (iow. memory+swap). This means we have to change them together.
1274 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1275 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1276 if (defined($wanted_memory) || defined($wanted_swap)) {
1278 $wanted_memory //= ($conf->{memory
} || 512);
1279 $wanted_swap //= ($conf->{swap
} || 0);
1281 my $total = $wanted_memory + $wanted_swap;
1283 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1284 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1286 $conf->{memory
} = $wanted_memory;
1287 $conf->{swap
} = $wanted_swap;
1289 write_config
($vmid, $conf) if $running;
1292 foreach my $opt (keys %$param) {
1293 my $value = $param->{$opt};
1294 if ($opt eq 'hostname') {
1295 $conf->{$opt} = $value;
1296 } elsif ($opt eq 'onboot') {
1297 $conf->{$opt} = $value ?
1 : 0;
1298 } elsif ($opt eq 'startup') {
1299 $conf->{$opt} = $value;
1300 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1301 next if $hotplug_error->($opt);
1302 $conf->{$opt} = $value;
1303 } elsif ($opt eq 'nameserver') {
1304 next if $hotplug_error->($opt);
1305 my $list = verify_nameserver_list
($value);
1306 $conf->{$opt} = $list;
1307 } elsif ($opt eq 'searchdomain') {
1308 next if $hotplug_error->($opt);
1309 my $list = verify_searchdomain_list
($value);
1310 $conf->{$opt} = $list;
1311 } elsif ($opt eq 'cpulimit') {
1312 next if $hotplug_error->($opt); # FIXME: hotplug
1313 $conf->{$opt} = $value;
1314 } elsif ($opt eq 'cpuunits') {
1315 $conf->{$opt} = $value;
1316 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1317 } elsif ($opt eq 'description') {
1318 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1319 } elsif ($opt =~ m/^net(\d+)$/) {
1321 my $net = parse_lxc_network
($value);
1323 $conf->{$opt} = print_lxc_network
($net);
1325 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1327 } elsif ($opt eq 'protection') {
1328 $conf->{$opt} = $value ?
1 : 0;
1329 } elsif ($opt =~ m/^mp(\d+)$/) {
1330 next if $hotplug_error->($opt);
1331 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1332 $conf->{$opt} = $value;
1334 } elsif ($opt eq 'rootfs') {
1335 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1336 die "implement me: $opt";
1337 } elsif ($opt eq 'unprivileged') {
1338 die "unable to modify read-only option: '$opt'\n";
1340 die "implement me: $opt";
1342 write_config
($vmid, $conf) if $running;
1345 if (@deleted_volumes) {
1346 my $storage_cfg = PVE
::Storage
::config
();
1347 foreach my $volume (@deleted_volumes) {
1348 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1353 my $storage_cfg = PVE
::Storage
::config
();
1354 create_disks
($storage_cfg, $vmid, $conf, $conf);
1357 # This should be the last thing we do here
1358 if ($running && scalar(@nohotplug)) {
1359 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1363 sub has_dev_console
{
1366 return !(defined($conf->{console
}) && !$conf->{console
});
1372 return $conf->{tty
} // $confdesc->{tty
}->{default};
1378 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1381 sub get_console_command
{
1382 my ($vmid, $conf) = @_;
1384 my $cmode = get_cmode
($conf);
1386 if ($cmode eq 'console') {
1387 return ['lxc-console', '-n', $vmid, '-t', 0];
1388 } elsif ($cmode eq 'tty') {
1389 return ['lxc-console', '-n', $vmid];
1390 } elsif ($cmode eq 'shell') {
1391 return ['lxc-attach', '--clear-env', '-n', $vmid];
1393 die "internal error";
1397 sub get_primary_ips
{
1400 # return data from net0
1402 return undef if !defined($conf->{net0
});
1403 my $net = parse_lxc_network
($conf->{net0
});
1405 my $ipv4 = $net->{ip
};
1407 if ($ipv4 =~ /^(dhcp|manual)$/) {
1413 my $ipv6 = $net->{ip6
};
1415 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1422 return ($ipv4, $ipv6);
1425 sub delete_mountpoint_volume
{
1426 my ($storage_cfg, $vmid, $volume) = @_;
1428 return if classify_mountpoint
($volume) ne 'volume';
1430 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1431 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1434 sub destroy_lxc_container
{
1435 my ($storage_cfg, $vmid, $conf) = @_;
1437 foreach_mountpoint
($conf, sub {
1438 my ($ms, $mountpoint) = @_;
1439 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1442 rmdir "/var/lib/lxc/$vmid/rootfs";
1443 unlink "/var/lib/lxc/$vmid/config";
1444 rmdir "/var/lib/lxc/$vmid";
1445 destroy_config
($vmid);
1447 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1448 #PVE::Tools::run_command($cmd);
1451 sub vm_stop_cleanup
{
1452 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1457 my $vollist = get_vm_volumes
($conf);
1458 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1461 warn $@ if $@; # avoid errors - just warn
1464 my $safe_num_ne = sub {
1467 return 0 if !defined($a) && !defined($b);
1468 return 1 if !defined($a);
1469 return 1 if !defined($b);
1474 my $safe_string_ne = sub {
1477 return 0 if !defined($a) && !defined($b);
1478 return 1 if !defined($a);
1479 return 1 if !defined($b);
1485 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1487 if ($newnet->{type
} ne 'veth') {
1488 # for when there are physical interfaces
1489 die "cannot update interface of type $newnet->{type}";
1492 my $veth = "veth${vmid}i${netid}";
1493 my $eth = $newnet->{name
};
1495 if (my $oldnetcfg = $conf->{$opt}) {
1496 my $oldnet = parse_lxc_network
($oldnetcfg);
1498 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1499 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1501 PVE
::Network
::veth_delete
($veth);
1502 delete $conf->{$opt};
1503 write_config
($vmid, $conf);
1505 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1507 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1508 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1509 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1511 if ($oldnet->{bridge
}) {
1512 PVE
::Network
::tap_unplug
($veth);
1513 foreach (qw(bridge tag firewall)) {
1514 delete $oldnet->{$_};
1516 $conf->{$opt} = print_lxc_network
($oldnet);
1517 write_config
($vmid, $conf);
1520 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1521 foreach (qw(bridge tag firewall)) {
1522 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1524 $conf->{$opt} = print_lxc_network
($oldnet);
1525 write_config
($vmid, $conf);
1528 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1531 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1535 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1537 my $veth = "veth${vmid}i${netid}";
1538 my $vethpeer = $veth . "p";
1539 my $eth = $newnet->{name
};
1541 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1542 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1544 # attach peer in container
1545 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1546 PVE
::Tools
::run_command
($cmd);
1548 # link up peer in container
1549 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1550 PVE
::Tools
::run_command
($cmd);
1552 my $done = { type
=> 'veth' };
1553 foreach (qw(bridge tag firewall hwaddr name)) {
1554 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1556 $conf->{$opt} = print_lxc_network
($done);
1558 write_config
($vmid, $conf);
1561 sub update_ipconfig
{
1562 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1564 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1566 my $optdata = parse_lxc_network
($conf->{$opt});
1570 my $cmdargs = shift;
1571 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1573 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1575 my $change_ip_config = sub {
1576 my ($ipversion) = @_;
1578 my $family_opt = "-$ipversion";
1579 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1580 my $gw= "gw$suffix";
1581 my $ip= "ip$suffix";
1583 my $newip = $newnet->{$ip};
1584 my $newgw = $newnet->{$gw};
1585 my $oldip = $optdata->{$ip};
1587 my $change_ip = &$safe_string_ne($oldip, $newip);
1588 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1590 return if !$change_ip && !$change_gw;
1592 # step 1: add new IP, if this fails we cancel
1593 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1594 if ($change_ip && $is_real_ip) {
1595 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1602 # step 2: replace gateway
1603 # If this fails we delete the added IP and cancel.
1604 # If it succeeds we save the config and delete the old IP, ignoring
1605 # errors. The config is then saved.
1606 # Note: 'ip route replace' can add
1610 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1611 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1613 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1617 # the route was not replaced, the old IP is still available
1618 # rollback (delete new IP) and cancel
1620 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1621 warn $@ if $@; # no need to die here
1626 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1627 # if the route was not deleted, the guest might have deleted it manually
1633 # from this point on we save the configuration
1634 # step 3: delete old IP ignoring errors
1635 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1636 # We need to enable promote_secondaries, otherwise our newly added
1637 # address will be removed along with the old one.
1640 if ($ipversion == 4) {
1641 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1642 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1643 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1645 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1647 warn $@ if $@; # no need to die here
1649 if ($ipversion == 4) {
1650 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1654 foreach my $property ($ip, $gw) {
1655 if ($newnet->{$property}) {
1656 $optdata->{$property} = $newnet->{$property};
1658 delete $optdata->{$property};
1661 $conf->{$opt} = print_lxc_network
($optdata);
1662 write_config
($vmid, $conf);
1663 $lxc_setup->setup_network($conf);
1666 &$change_ip_config(4);
1667 &$change_ip_config(6);
1671 # Internal snapshots
1673 # NOTE: Snapshot create/delete involves several non-atomic
1674 # action, and can take a long time.
1675 # So we try to avoid locking the file and use 'lock' variable
1676 # inside the config file instead.
1678 my $snapshot_copy_config = sub {
1679 my ($source, $dest) = @_;
1681 foreach my $k (keys %$source) {
1682 next if $k eq 'snapshots';
1683 next if $k eq 'snapstate';
1684 next if $k eq 'snaptime';
1685 next if $k eq 'vmstate';
1686 next if $k eq 'lock';
1687 next if $k eq 'digest';
1688 next if $k eq 'description';
1690 $dest->{$k} = $source->{$k};
1694 my $snapshot_prepare = sub {
1695 my ($vmid, $snapname, $comment) = @_;
1699 my $updatefn = sub {
1701 my $conf = load_config
($vmid);
1703 die "you can't take a snapshot if it's a template\n"
1704 if is_template
($conf);
1708 $conf->{lock} = 'snapshot';
1710 die "snapshot name '$snapname' already used\n"
1711 if defined($conf->{snapshots
}->{$snapname});
1713 my $storecfg = PVE
::Storage
::config
();
1714 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1716 $snap = $conf->{snapshots
}->{$snapname} = {};
1718 &$snapshot_copy_config($conf, $snap);
1720 $snap->{'snapstate'} = "prepare";
1721 $snap->{'snaptime'} = time();
1722 $snap->{'description'} = $comment if $comment;
1723 $conf->{snapshots
}->{$snapname} = $snap;
1725 write_config
($vmid, $conf);
1728 lock_container
($vmid, 10, $updatefn);
1733 my $snapshot_commit = sub {
1734 my ($vmid, $snapname) = @_;
1736 my $updatefn = sub {
1738 my $conf = load_config
($vmid);
1740 die "missing snapshot lock\n"
1741 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1743 die "snapshot '$snapname' does not exist\n"
1744 if !defined($conf->{snapshots
}->{$snapname});
1746 die "wrong snapshot state\n"
1747 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1748 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1750 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1751 delete $conf->{lock};
1752 $conf->{parent
} = $snapname;
1754 write_config
($vmid, $conf);
1757 lock_container
($vmid, 10 ,$updatefn);
1761 my ($feature, $conf, $storecfg, $snapname) = @_;
1765 foreach_mountpoint
($conf, sub {
1766 my ($ms, $mountpoint) = @_;
1768 return if $err; # skip further test
1770 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1772 # TODO: implement support for mountpoints
1773 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1777 return $err ?
0 : 1;
1780 sub snapshot_create
{
1781 my ($vmid, $snapname, $comment) = @_;
1783 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1785 my $conf = load_config
($vmid);
1787 my $running = check_running
($vmid);
1790 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1791 PVE
::Tools
::run_command
(['/bin/sync']);
1794 my $storecfg = PVE
::Storage
::config
();
1795 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1796 my $volid = $rootinfo->{volume
};
1799 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1802 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1803 &$snapshot_commit($vmid, $snapname);
1806 snapshot_delete
($vmid, $snapname, 1);
1811 sub snapshot_delete
{
1812 my ($vmid, $snapname, $force) = @_;
1818 my $updatefn = sub {
1820 $conf = load_config
($vmid);
1822 die "you can't delete a snapshot if vm is a template\n"
1823 if is_template
($conf);
1825 $snap = $conf->{snapshots
}->{$snapname};
1829 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1831 $snap->{snapstate
} = 'delete';
1833 write_config
($vmid, $conf);
1836 lock_container
($vmid, 10, $updatefn);
1838 my $storecfg = PVE
::Storage
::config
();
1840 my $del_snap = sub {
1844 if ($conf->{parent
} eq $snapname) {
1845 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1846 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1848 delete $conf->{parent
};
1852 delete $conf->{snapshots
}->{$snapname};
1854 write_config
($vmid, $conf);
1857 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1858 my $rootinfo = parse_ct_mountpoint
($rootfs);
1859 my $volid = $rootinfo->{volume
};
1862 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1866 if(!$err || ($err && $force)) {
1867 lock_container
($vmid, 10, $del_snap);
1869 die "Can't delete snapshot: $vmid $snapname $err\n";
1874 sub snapshot_rollback
{
1875 my ($vmid, $snapname) = @_;
1877 my $storecfg = PVE
::Storage
::config
();
1879 my $conf = load_config
($vmid);
1881 die "you can't rollback if vm is a template\n" if is_template
($conf);
1883 my $snap = $conf->{snapshots
}->{$snapname};
1885 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1887 my $rootfs = $snap->{rootfs
};
1888 my $rootinfo = parse_ct_mountpoint
($rootfs);
1889 my $volid = $rootinfo->{volume
};
1891 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1893 my $updatefn = sub {
1895 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1896 if $snap->{snapstate
};
1900 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1902 die "unable to rollback vm $vmid: vm is running\n"
1903 if check_running
($vmid);
1905 $conf->{lock} = 'rollback';
1909 # copy snapshot config to current config
1911 my $tmp_conf = $conf;
1912 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1913 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1914 delete $conf->{snaptime
};
1915 delete $conf->{snapname
};
1916 $conf->{parent
} = $snapname;
1918 write_config
($vmid, $conf);
1921 my $unlockfn = sub {
1922 delete $conf->{lock};
1923 write_config
($vmid, $conf);
1926 lock_container
($vmid, 10, $updatefn);
1928 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1930 lock_container
($vmid, 5, $unlockfn);
1933 sub template_create
{
1934 my ($vmid, $conf) = @_;
1936 my $storecfg = PVE
::Storage
::config
();
1938 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1939 my $volid = $rootinfo->{volume
};
1941 die "Template feature is not available for '$volid'\n"
1942 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1944 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1946 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1947 $rootinfo->{volume
} = $template_volid;
1948 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1950 write_config
($vmid, $conf);
1956 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1959 sub mountpoint_names
{
1962 my @names = ('rootfs');
1964 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1965 push @names, "mp$i";
1968 return $reverse ?
reverse @names : @names;
1971 # The container might have *different* symlinks than the host. realpath/abs_path
1972 # use the actual filesystem to resolve links.
1973 sub sanitize_mountpoint
{
1975 $mp = '/' . $mp; # we always start with a slash
1976 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1977 $mp =~ s
@/\./@@g; # collapse /./
1978 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1979 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1980 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1984 sub foreach_mountpoint_full
{
1985 my ($conf, $reverse, $func) = @_;
1987 foreach my $key (mountpoint_names
($reverse)) {
1988 my $value = $conf->{$key};
1989 next if !defined($value);
1990 my $mountpoint = parse_ct_mountpoint
($value, 1);
1991 next if !defined($mountpoint);
1993 # just to be sure: rootfs is /
1994 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1995 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1997 $path = $mountpoint->{volume
};
1998 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2000 &$func($key, $mountpoint);
2004 sub foreach_mountpoint
{
2005 my ($conf, $func) = @_;
2007 foreach_mountpoint_full
($conf, 0, $func);
2010 sub foreach_mountpoint_reverse
{
2011 my ($conf, $func) = @_;
2013 foreach_mountpoint_full
($conf, 1, $func);
2016 sub check_ct_modify_config_perm
{
2017 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2019 return 1 if $authuser ne 'root@pam';
2021 foreach my $opt (@$key_list) {
2023 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2024 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2025 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2026 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2027 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2028 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2029 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2030 $opt eq 'searchdomain' || $opt eq 'hostname') {
2031 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2033 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2041 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2043 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2044 my $volid_list = get_vm_volumes
($conf);
2046 foreach_mountpoint_reverse
($conf, sub {
2047 my ($ms, $mountpoint) = @_;
2049 my $volid = $mountpoint->{volume
};
2050 my $mount = $mountpoint->{mp
};
2052 return if !$volid || !$mount;
2054 my $mount_path = "$rootdir/$mount";
2055 $mount_path =~ s!/+!/!g;
2057 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2060 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2073 my ($vmid, $storage_cfg, $conf) = @_;
2075 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2076 File
::Path
::make_path
($rootdir);
2078 my $volid_list = get_vm_volumes
($conf);
2079 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2082 foreach_mountpoint
($conf, sub {
2083 my ($ms, $mountpoint) = @_;
2085 my $volid = $mountpoint->{volume
};
2086 my $mount = $mountpoint->{mp
};
2088 return if !$volid || !$mount;
2090 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2091 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2092 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2094 die "unable to mount base volume - internal error" if $isBase;
2096 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2100 warn "mounting container failed - $err";
2101 umount_all
($vmid, $storage_cfg, $conf, 1);
2108 sub mountpoint_mount_path
{
2109 my ($mountpoint, $storage_cfg, $snapname) = @_;
2111 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2114 my $check_mount_path = sub {
2116 $path = File
::Spec-
>canonpath($path);
2117 my $real = Cwd
::realpath
($path);
2118 if ($real ne $path) {
2119 die "mount path modified by symlink: $path != $real";
2128 if ($line =~ m
@^(/dev/loop\d
+):@) {
2132 my $cmd = ['losetup', '--associated', $path];
2133 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2137 # use $rootdir = undef to just return the corresponding mount path
2138 sub mountpoint_mount
{
2139 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2141 my $volid = $mountpoint->{volume
};
2142 my $mount = $mountpoint->{mp
};
2143 my $type = $mountpoint->{type
};
2145 return if !$volid || !$mount;
2149 if (defined($rootdir)) {
2150 $rootdir =~ s!/+$!!;
2151 $mount_path = "$rootdir/$mount";
2152 $mount_path =~ s!/+!/!g;
2153 &$check_mount_path($mount_path);
2154 File
::Path
::mkpath
($mount_path);
2157 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2159 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2163 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2164 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2166 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2167 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2169 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2171 if ($format eq 'subvol') {
2174 if ($scfg->{type
} eq 'zfspool') {
2175 my $path_arg = $path;
2176 $path_arg =~ s!^/+!!;
2177 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2179 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2182 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2185 return wantarray ?
($path, 0) : $path;
2186 } elsif ($format eq 'raw' || $format eq 'iso') {
2187 my $use_loopdev = 0;
2189 if ($scfg->{path
}) {
2190 push @extra_opts, '-o', 'loop';
2192 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2195 die "unsupported storage type '$scfg->{type}'\n";
2198 if ($format eq 'iso') {
2199 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2200 } elsif ($isBase || defined($snapname)) {
2201 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2203 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2206 return wantarray ?
($path, $use_loopdev) : $path;
2208 die "unsupported image format '$format'\n";
2210 } elsif ($type eq 'device') {
2211 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2212 return wantarray ?
($volid, 0) : $volid;
2213 } elsif ($type eq 'bind' && -d
$volid) {
2214 &$check_mount_path($volid);
2215 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2216 return wantarray ?
($volid, 0) : $volid;
2219 die "unsupported storage";
2222 sub get_vm_volumes
{
2223 my ($conf, $excludes) = @_;
2227 foreach_mountpoint
($conf, sub {
2228 my ($ms, $mountpoint) = @_;
2230 return if $excludes && $ms eq $excludes;
2232 my $volid = $mountpoint->{volume
};
2234 return if !$volid || $mountpoint->{type
} ne 'volume';
2236 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2239 push @$vollist, $volid;
2246 my ($dev, $rootuid, $rootgid) = @_;
2248 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2249 '-E', "root_owner=$rootuid:$rootgid",
2254 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2256 if ($volid =~ m!^/dev/.+!) {
2261 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2263 die "cannot format volume '$volid' with no storage\n" if !$storage;
2265 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2267 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2269 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2270 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2272 die "cannot format volume '$volid' (format == $format)\n"
2273 if $format ne 'raw';
2275 mkfs
($path, $rootuid, $rootgid);
2279 my ($storecfg, $vollist) = @_;
2281 foreach my $volid (@$vollist) {
2282 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2288 my ($storecfg, $vmid, $settings, $conf) = @_;
2293 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2294 my $chown_vollist = [];
2296 foreach_mountpoint
($settings, sub {
2297 my ($ms, $mountpoint) = @_;
2299 my $volid = $mountpoint->{volume
};
2300 my $mp = $mountpoint->{mp
};
2302 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2304 return if !$storage;
2306 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2307 my ($storeid, $size_gb) = ($1, $2);
2309 my $size_kb = int(${size_gb
}*1024) * 1024;
2311 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2312 # fixme: use better naming ct-$vmid-disk-X.raw?
2314 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2316 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2318 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2320 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2322 push @$chown_vollist, $volid;
2324 } elsif ($scfg->{type
} eq 'zfspool') {
2326 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2328 push @$chown_vollist, $volid;
2329 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2331 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2332 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2334 } elsif ($scfg->{type
} eq 'rbd') {
2336 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2337 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2338 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2340 die "unable to create containers on storage type '$scfg->{type}'\n";
2342 push @$vollist, $volid;
2343 $mountpoint->{volume
} = $volid;
2344 $mountpoint->{size
} = $size_kb * 1024;
2345 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2347 # use specified/existing volid
2351 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2352 foreach my $volid (@$chown_vollist) {
2353 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2354 chown($rootuid, $rootgid, $path);
2356 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2358 # free allocated images on error
2360 destroy_disks
($storecfg, $vollist);
2366 # bash completion helper
2368 sub complete_os_templates
{
2369 my ($cmdname, $pname, $cvalue) = @_;
2371 my $cfg = PVE
::Storage
::config
();
2375 if ($cvalue =~ m/^([^:]+):/) {
2379 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2380 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2383 foreach my $id (keys %$data) {
2384 foreach my $item (@{$data->{$id}}) {
2385 push @$res, $item->{volid
} if defined($item->{volid
});
2392 my $complete_ctid_full = sub {
2395 my $idlist = vmstatus
();
2397 my $active_hash = list_active_containers
();
2401 foreach my $id (keys %$idlist) {
2402 my $d = $idlist->{$id};
2403 if (defined($running)) {
2404 next if $d->{template
};
2405 next if $running && !$active_hash->{$id};
2406 next if !$running && $active_hash->{$id};
2415 return &$complete_ctid_full();
2418 sub complete_ctid_stopped
{
2419 return &$complete_ctid_full(0);
2422 sub complete_ctid_running
{
2423 return &$complete_ctid_full(1);
2433 my $lxc = $conf->{lxc
};
2434 foreach my $entry (@$lxc) {
2435 my ($key, $value) = @$entry;
2436 next if $key ne 'lxc.id_map';
2437 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2438 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2439 push @$id_map, [$type, $ct, $host, $length];
2441 $rootuid = $host if $type eq 'u';
2442 $rootgid = $host if $type eq 'g';
2445 die "failed to parse id_map: $value\n";
2449 if (!@$id_map && $conf->{unprivileged
}) {
2450 # Should we read them from /etc/subuid?
2451 $id_map = [ ['u', '0', '100000', '65536'],
2452 ['g', '0', '100000', '65536'] ];
2453 $rootuid = $rootgid = 100000;
2456 return ($id_map, $rootuid, $rootgid);
2459 sub userns_command
{
2462 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];