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.',
362 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
366 type
=> 'string', format
=> 'pve-volume-id',
367 description
=> "Reference to unused volumes.",
370 my $MAX_MOUNT_POINTS = 10;
371 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
372 $confdesc->{"mp$i"} = {
374 type
=> 'string', format
=> $mp_desc,
375 description
=> "Use volume as container mount point (experimental feature).",
380 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
381 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
382 $confdesc->{"unused$i"} = $unuseddesc;
385 sub write_pct_config
{
386 my ($filename, $conf) = @_;
388 delete $conf->{snapstate
}; # just to be sure
390 my $generate_raw_config = sub {
395 # add description as comment to top of file
396 my $descr = $conf->{description
} || '';
397 foreach my $cl (split(/\n/, $descr)) {
398 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
401 foreach my $key (sort keys %$conf) {
402 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
403 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
404 my $value = $conf->{$key};
405 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
406 $raw .= "$key: $value\n";
409 if (my $lxcconf = $conf->{lxc
}) {
410 foreach my $entry (@$lxcconf) {
411 my ($k, $v) = @$entry;
419 my $raw = &$generate_raw_config($conf);
421 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
422 $raw .= "\n[$snapname]\n";
423 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
430 my ($key, $value) = @_;
432 die "unknown setting '$key'\n" if !$confdesc->{$key};
434 my $type = $confdesc->{$key}->{type
};
436 if (!defined($value)) {
437 die "got undefined value\n";
440 if ($value =~ m/[\n\r]/) {
441 die "property contains a line feed\n";
444 if ($type eq 'boolean') {
445 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
446 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
447 die "type check ('boolean') failed - got '$value'\n";
448 } elsif ($type eq 'integer') {
449 return int($1) if $value =~ m/^(\d+)$/;
450 die "type check ('integer') failed - got '$value'\n";
451 } elsif ($type eq 'number') {
452 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
453 die "type check ('number') failed - got '$value'\n";
454 } elsif ($type eq 'string') {
455 if (my $fmt = $confdesc->{$key}->{format
}) {
456 PVE
::JSONSchema
::check_format
($fmt, $value);
465 sub parse_pct_config
{
466 my ($filename, $raw) = @_;
468 return undef if !defined($raw);
471 digest
=> Digest
::SHA
::sha1_hex
($raw),
475 $filename =~ m
|/lxc/(\d
+).conf
$|
476 || die "got strange filename '$filename'";
484 my @lines = split(/\n/, $raw);
485 foreach my $line (@lines) {
486 next if $line =~ m/^\s*$/;
488 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
490 $conf->{description
} = $descr if $descr;
492 $conf = $res->{snapshots
}->{$section} = {};
496 if ($line =~ m/^\#(.*)\s*$/) {
497 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
501 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
504 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
505 push @{$conf->{lxc
}}, [$key, $value];
507 warn "vm $vmid - unable to parse config: $line\n";
509 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
510 $descr .= PVE
::Tools
::decode_text
($2);
511 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
512 $conf->{snapstate
} = $1;
513 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
516 eval { $value = check_type
($key, $value); };
517 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
518 $conf->{$key} = $value;
520 warn "vm $vmid - unable to parse config: $line\n";
524 $conf->{description
} = $descr if $descr;
526 delete $res->{snapstate
}; # just to be sure
532 my $vmlist = PVE
::Cluster
::get_vmlist
();
534 return $res if !$vmlist || !$vmlist->{ids
};
535 my $ids = $vmlist->{ids
};
537 foreach my $vmid (keys %$ids) {
538 next if !$vmid; # skip CT0
539 my $d = $ids->{$vmid};
540 next if !$d->{node
} || $d->{node
} ne $nodename;
541 next if !$d->{type
} || $d->{type
} ne 'lxc';
542 $res->{$vmid}->{type
} = 'lxc';
547 sub cfs_config_path
{
548 my ($vmid, $node) = @_;
550 $node = $nodename if !$node;
551 return "nodes/$node/lxc/$vmid.conf";
555 my ($vmid, $node) = @_;
557 my $cfspath = cfs_config_path
($vmid, $node);
558 return "/etc/pve/$cfspath";
562 my ($vmid, $node) = @_;
564 $node = $nodename if !$node;
565 my $cfspath = cfs_config_path
($vmid, $node);
567 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
568 die "container $vmid does not exists\n" if !defined($conf);
574 my ($vmid, $conf) = @_;
576 my $dir = "/etc/pve/nodes/$nodename/lxc";
579 write_config
($vmid, $conf);
585 unlink config_file
($vmid, $nodename);
589 my ($vmid, $conf) = @_;
591 my $cfspath = cfs_config_path
($vmid);
593 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
596 # flock: we use one file handle per process, so lock file
597 # can be called multiple times and succeeds for the same process.
599 my $lock_handles = {};
600 my $lockdir = "/run/lock/lxc";
605 return "$lockdir/pve-config-${vmid}.lock";
609 my ($vmid, $timeout) = @_;
611 $timeout = 10 if !$timeout;
614 my $filename = lock_filename
($vmid);
616 mkdir $lockdir if !-d
$lockdir;
618 my $lock_func = sub {
619 if (!$lock_handles->{$$}->{$filename}) {
620 my $fh = new IO
::File
(">>$filename") ||
621 die "can't open file - $!\n";
622 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
625 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
626 print STDERR
"trying to aquire lock...";
629 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
630 # try again on EINTR (see bug #273)
631 if ($success || ($! != EINTR
)) {
636 print STDERR
" failed\n";
637 die "can't aquire lock - $!\n";
640 print STDERR
" OK\n";
643 $lock_handles->{$$}->{$filename}->{refcount
}++;
646 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
649 die "can't lock file '$filename' - $err";
656 my $filename = lock_filename
($vmid);
658 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
659 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
660 if ($refcount <= 0) {
661 $lock_handles->{$$}->{$filename} = undef;
668 my ($vmid, $timeout, $code, @param) = @_;
672 lock_aquire
($vmid, $timeout);
673 eval { $res = &$code(@param) };
685 return defined($confdesc->{$name});
688 # add JSON properties for create and set function
689 sub json_config_properties
{
692 foreach my $opt (keys %$confdesc) {
693 next if $opt eq 'parent' || $opt eq 'snaptime';
694 next if $prop->{$opt};
695 $prop->{$opt} = $confdesc->{$opt};
701 sub json_config_properties_no_rootfs
{
704 foreach my $opt (keys %$confdesc) {
705 next if $prop->{$opt};
706 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
707 $prop->{$opt} = $confdesc->{$opt};
713 # container status helpers
715 sub list_active_containers
{
717 my $filename = "/proc/net/unix";
719 # similar test is used by lcxcontainers.c: list_active_containers
722 my $fh = IO
::File-
>new ($filename, "r");
725 while (defined(my $line = <$fh>)) {
726 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
728 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
739 # warning: this is slow
743 my $active_hash = list_active_containers
();
745 return 1 if defined($active_hash->{$vmid});
750 sub get_container_disk_usage
{
751 my ($vmid, $pid) = @_;
753 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
756 my $last_proc_vmid_stat;
758 my $parse_cpuacct_stat = sub {
761 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
765 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
778 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
780 my $active_hash = list_active_containers
();
782 my $cpucount = $cpuinfo->{cpus
} || 1;
784 my $cdtime = gettimeofday
;
786 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
788 foreach my $vmid (keys %$list) {
789 my $d = $list->{$vmid};
791 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
792 warn $@ if $@; # ignore errors (consider them stopped)
794 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
796 my $cfspath = cfs_config_path
($vmid);
797 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
799 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
800 $d->{name
} =~ s/[\s]//g;
802 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
805 my $res = get_container_disk_usage
($vmid, $d->{pid
});
806 $d->{disk
} = $res->{used
};
807 $d->{maxdisk
} = $res->{total
};
810 # use 4GB by default ??
811 if (my $rootfs = $conf->{rootfs
}) {
812 my $rootinfo = parse_ct_mountpoint
($rootfs);
813 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
815 $d->{maxdisk
} = 4*1024*1024*1024;
821 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
822 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
833 $d->{template
} = is_template
($conf);
836 foreach my $vmid (keys %$list) {
837 my $d = $list->{$vmid};
840 next if !$pid; # skip stopped CTs
842 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
843 $d->{uptime
} = time - $ctime; # the method lxcfs uses
845 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
846 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
848 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
849 my @bytes = split(/\n/, $blkio_bytes);
850 foreach my $byte (@bytes) {
851 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
852 $d->{diskread
} = $2 if $key eq 'Read';
853 $d->{diskwrite
} = $2 if $key eq 'Write';
857 my $pstat = &$parse_cpuacct_stat($vmid);
859 my $used = $pstat->{utime} + $pstat->{stime
};
861 my $old = $last_proc_vmid_stat->{$vmid};
863 $last_proc_vmid_stat->{$vmid} = {
871 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
874 my $dutime = $used - $old->{used
};
876 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
877 $last_proc_vmid_stat->{$vmid} = {
883 $d->{cpu
} = $old->{cpu
};
887 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
889 foreach my $dev (keys %$netdev) {
890 next if $dev !~ m/^veth([1-9]\d*)i/;
892 my $d = $list->{$vmid};
896 $d->{netout
} += $netdev->{$dev}->{receive
};
897 $d->{netin
} += $netdev->{$dev}->{transmit
};
904 sub classify_mountpoint
{
907 return 'device' if $vol =~ m!^/dev/!;
913 sub parse_ct_mountpoint
{
914 my ($data, $noerr) = @_;
919 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
921 return undef if $noerr;
925 if (defined(my $size = $res->{size
})) {
926 $size = PVE
::JSONSchema
::parse_size
($size);
927 if (!defined($size)) {
928 return undef if $noerr;
929 die "invalid size: $size\n";
931 $res->{size
} = $size;
934 $res->{type
} = classify_mountpoint
($res->{volume
});
939 sub print_ct_mountpoint
{
940 my ($info, $nomp) = @_;
941 my $skip = [ 'type' ];
942 push @$skip, 'mp' if $nomp;
943 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
946 sub print_lxc_network
{
948 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
951 sub parse_lxc_network
{
956 return $res if !$data;
958 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
960 $res->{type
} = 'veth';
961 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
966 sub read_cgroup_value
{
967 my ($group, $vmid, $name, $full) = @_;
969 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
971 return PVE
::Tools
::file_get_contents
($path) if $full;
973 return PVE
::Tools
::file_read_firstline
($path);
976 sub write_cgroup_value
{
977 my ($group, $vmid, $name, $value) = @_;
979 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
980 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
984 sub find_lxc_console_pids
{
988 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
991 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
994 my @args = split(/\0/, $cmdline);
996 # serach for lxc-console -n <vmid>
997 return if scalar(@args) != 3;
998 return if $args[1] ne '-n';
999 return if $args[2] !~ m/^\d+$/;
1000 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1002 my $vmid = $args[2];
1004 push @{$res->{$vmid}}, $pid;
1016 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1018 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1020 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1025 # Note: we cannot use Net:IP, because that only allows strict
1027 sub parse_ipv4_cidr
{
1028 my ($cidr, $noerr) = @_;
1030 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1031 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1034 return undef if $noerr;
1036 die "unable to parse ipv4 address/mask\n";
1042 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1045 sub check_protection
{
1046 my ($vm_conf, $err_msg) = @_;
1048 if ($vm_conf->{protection
}) {
1049 die "$err_msg - protection mode enabled\n";
1053 sub update_lxc_config
{
1054 my ($storage_cfg, $vmid, $conf) = @_;
1056 my $dir = "/var/lib/lxc/$vmid";
1058 if ($conf->{template
}) {
1060 unlink "$dir/config";
1067 die "missing 'arch' - internal error" if !$conf->{arch
};
1068 $raw .= "lxc.arch = $conf->{arch}\n";
1070 my $unprivileged = $conf->{unprivileged
};
1071 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1073 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1074 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
1075 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1076 if ($unprivileged || $custom_idmap) {
1077 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1080 die "implement me (ostype $ostype)";
1083 $raw .= "lxc.monitor.unshare = 1\n";
1085 # Should we read them from /etc/subuid?
1086 if ($unprivileged && !$custom_idmap) {
1087 $raw .= "lxc.id_map = u 0 100000 65536\n";
1088 $raw .= "lxc.id_map = g 0 100000 65536\n";
1091 if (!has_dev_console
($conf)) {
1092 $raw .= "lxc.console = none\n";
1093 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1096 my $ttycount = get_tty_count
($conf);
1097 $raw .= "lxc.tty = $ttycount\n";
1099 # some init scripts expects a linux terminal (turnkey).
1100 $raw .= "lxc.environment = TERM=linux\n";
1102 my $utsname = $conf->{hostname
} || "CT$vmid";
1103 $raw .= "lxc.utsname = $utsname\n";
1105 my $memory = $conf->{memory
} || 512;
1106 my $swap = $conf->{swap
} // 0;
1108 my $lxcmem = int($memory*1024*1024);
1109 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1111 my $lxcswap = int(($memory + $swap)*1024*1024);
1112 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1114 if (my $cpulimit = $conf->{cpulimit
}) {
1115 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1116 my $value = int(100000*$cpulimit);
1117 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1120 my $shares = $conf->{cpuunits
} || 1024;
1121 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1123 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1124 $mountpoint->{mp
} = '/';
1126 $raw .= "lxc.rootfs = $dir/rootfs\n";
1129 foreach my $k (keys %$conf) {
1130 next if $k !~ m/^net(\d+)$/;
1132 my $d = parse_lxc_network
($conf->{$k});
1134 $raw .= "lxc.network.type = veth\n";
1135 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1136 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1137 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1138 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1141 if (my $lxcconf = $conf->{lxc
}) {
1142 foreach my $entry (@$lxcconf) {
1143 my ($k, $v) = @$entry;
1144 $netcount++ if $k eq 'lxc.network.type';
1145 $raw .= "$k = $v\n";
1149 $raw .= "lxc.network.type = empty\n" if !$netcount;
1151 File
::Path
::mkpath
("$dir/rootfs");
1153 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1156 # verify and cleanup nameserver list (replace \0 with ' ')
1157 sub verify_nameserver_list
{
1158 my ($nameserver_list) = @_;
1161 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1162 PVE
::JSONSchema
::pve_verify_ip
($server);
1163 push @list, $server;
1166 return join(' ', @list);
1169 sub verify_searchdomain_list
{
1170 my ($searchdomain_list) = @_;
1173 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1174 # todo: should we add checks for valid dns domains?
1175 push @list, $server;
1178 return join(' ', @list);
1181 sub add_unused_volume
{
1182 my ($config, $volid) = @_;
1185 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1186 my $test = "unused$ind";
1187 if (my $vid = $config->{$test}) {
1188 return if $vid eq $volid; # do not add duplicates
1194 die "To many unused volume - please delete them first.\n" if !$key;
1196 $config->{$key} = $volid;
1201 sub update_pct_config
{
1202 my ($vmid, $conf, $running, $param, $delete) = @_;
1207 my @deleted_volumes;
1211 my $pid = find_lxc_pid
($vmid);
1212 $rootdir = "/proc/$pid/root";
1215 my $hotplug_error = sub {
1217 push @nohotplug, @_;
1224 if (defined($delete)) {
1225 foreach my $opt (@$delete) {
1226 if (!exists($conf->{$opt})) {
1227 warn "no such option: $opt\n";
1231 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1232 die "unable to delete required option '$opt'\n";
1233 } elsif ($opt eq 'swap') {
1234 delete $conf->{$opt};
1235 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1236 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1237 delete $conf->{$opt};
1238 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1239 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1240 next if $hotplug_error->($opt);
1241 delete $conf->{$opt};
1242 } elsif ($opt =~ m/^net(\d)$/) {
1243 delete $conf->{$opt};
1246 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1247 } elsif ($opt eq 'protection') {
1248 delete $conf->{$opt};
1249 } elsif ($opt =~ m/^unused(\d+)$/) {
1250 next if $hotplug_error->($opt);
1251 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1252 push @deleted_volumes, $conf->{$opt};
1253 delete $conf->{$opt};
1254 } elsif ($opt =~ m/^mp(\d+)$/) {
1255 next if $hotplug_error->($opt);
1256 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1257 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1258 if ($mountpoint->{type
} eq 'volume') {
1259 add_unused_volume
($conf, $mountpoint->{volume
})
1261 delete $conf->{$opt};
1262 } elsif ($opt eq 'unprivileged') {
1263 die "unable to delete read-only option: '$opt'\n";
1265 die "implement me (delete: $opt)"
1267 write_config
($vmid, $conf) if $running;
1271 # There's no separate swap size to configure, there's memory and "total"
1272 # memory (iow. memory+swap). This means we have to change them together.
1273 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1274 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1275 if (defined($wanted_memory) || defined($wanted_swap)) {
1277 $wanted_memory //= ($conf->{memory
} || 512);
1278 $wanted_swap //= ($conf->{swap
} || 0);
1280 my $total = $wanted_memory + $wanted_swap;
1282 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1283 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1285 $conf->{memory
} = $wanted_memory;
1286 $conf->{swap
} = $wanted_swap;
1288 write_config
($vmid, $conf) if $running;
1291 foreach my $opt (keys %$param) {
1292 my $value = $param->{$opt};
1293 if ($opt eq 'hostname') {
1294 $conf->{$opt} = $value;
1295 } elsif ($opt eq 'onboot') {
1296 $conf->{$opt} = $value ?
1 : 0;
1297 } elsif ($opt eq 'startup') {
1298 $conf->{$opt} = $value;
1299 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1300 next if $hotplug_error->($opt);
1301 $conf->{$opt} = $value;
1302 } elsif ($opt eq 'nameserver') {
1303 next if $hotplug_error->($opt);
1304 my $list = verify_nameserver_list
($value);
1305 $conf->{$opt} = $list;
1306 } elsif ($opt eq 'searchdomain') {
1307 next if $hotplug_error->($opt);
1308 my $list = verify_searchdomain_list
($value);
1309 $conf->{$opt} = $list;
1310 } elsif ($opt eq 'cpulimit') {
1311 next if $hotplug_error->($opt); # FIXME: hotplug
1312 $conf->{$opt} = $value;
1313 } elsif ($opt eq 'cpuunits') {
1314 $conf->{$opt} = $value;
1315 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1316 } elsif ($opt eq 'description') {
1317 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1318 } elsif ($opt =~ m/^net(\d+)$/) {
1320 my $net = parse_lxc_network
($value);
1322 $conf->{$opt} = print_lxc_network
($net);
1324 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1326 } elsif ($opt eq 'protection') {
1327 $conf->{$opt} = $value ?
1 : 0;
1328 } elsif ($opt =~ m/^mp(\d+)$/) {
1329 next if $hotplug_error->($opt);
1330 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1331 $conf->{$opt} = $value;
1333 } elsif ($opt eq 'rootfs') {
1334 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1335 die "implement me: $opt";
1336 } elsif ($opt eq 'unprivileged') {
1337 die "unable to modify read-only option: '$opt'\n";
1339 die "implement me: $opt";
1341 write_config
($vmid, $conf) if $running;
1344 if (@deleted_volumes) {
1345 my $storage_cfg = PVE
::Storage
::config
();
1346 foreach my $volume (@deleted_volumes) {
1347 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1352 my $storage_cfg = PVE
::Storage
::config
();
1353 create_disks
($storage_cfg, $vmid, $conf, $conf);
1356 # This should be the last thing we do here
1357 if ($running && scalar(@nohotplug)) {
1358 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1362 sub has_dev_console
{
1365 return !(defined($conf->{console
}) && !$conf->{console
});
1371 return $conf->{tty
} // $confdesc->{tty
}->{default};
1377 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1380 sub get_console_command
{
1381 my ($vmid, $conf) = @_;
1383 my $cmode = get_cmode
($conf);
1385 if ($cmode eq 'console') {
1386 return ['lxc-console', '-n', $vmid, '-t', 0];
1387 } elsif ($cmode eq 'tty') {
1388 return ['lxc-console', '-n', $vmid];
1389 } elsif ($cmode eq 'shell') {
1390 return ['lxc-attach', '--clear-env', '-n', $vmid];
1392 die "internal error";
1396 sub get_primary_ips
{
1399 # return data from net0
1401 return undef if !defined($conf->{net0
});
1402 my $net = parse_lxc_network
($conf->{net0
});
1404 my $ipv4 = $net->{ip
};
1406 if ($ipv4 =~ /^(dhcp|manual)$/) {
1412 my $ipv6 = $net->{ip6
};
1414 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1421 return ($ipv4, $ipv6);
1424 sub delete_mountpoint_volume
{
1425 my ($storage_cfg, $vmid, $volume) = @_;
1427 return if classify_mountpoint
($volume) ne 'volume';
1429 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1430 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1433 sub destroy_lxc_container
{
1434 my ($storage_cfg, $vmid, $conf) = @_;
1436 foreach_mountpoint
($conf, sub {
1437 my ($ms, $mountpoint) = @_;
1438 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1441 rmdir "/var/lib/lxc/$vmid/rootfs";
1442 unlink "/var/lib/lxc/$vmid/config";
1443 rmdir "/var/lib/lxc/$vmid";
1444 destroy_config
($vmid);
1446 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1447 #PVE::Tools::run_command($cmd);
1450 sub vm_stop_cleanup
{
1451 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1456 my $vollist = get_vm_volumes
($conf);
1457 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1460 warn $@ if $@; # avoid errors - just warn
1463 my $safe_num_ne = sub {
1466 return 0 if !defined($a) && !defined($b);
1467 return 1 if !defined($a);
1468 return 1 if !defined($b);
1473 my $safe_string_ne = sub {
1476 return 0 if !defined($a) && !defined($b);
1477 return 1 if !defined($a);
1478 return 1 if !defined($b);
1484 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1486 if ($newnet->{type
} ne 'veth') {
1487 # for when there are physical interfaces
1488 die "cannot update interface of type $newnet->{type}";
1491 my $veth = "veth${vmid}i${netid}";
1492 my $eth = $newnet->{name
};
1494 if (my $oldnetcfg = $conf->{$opt}) {
1495 my $oldnet = parse_lxc_network
($oldnetcfg);
1497 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1498 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1500 PVE
::Network
::veth_delete
($veth);
1501 delete $conf->{$opt};
1502 write_config
($vmid, $conf);
1504 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1506 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1507 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1508 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1510 if ($oldnet->{bridge
}) {
1511 PVE
::Network
::tap_unplug
($veth);
1512 foreach (qw(bridge tag firewall)) {
1513 delete $oldnet->{$_};
1515 $conf->{$opt} = print_lxc_network
($oldnet);
1516 write_config
($vmid, $conf);
1519 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1520 foreach (qw(bridge tag firewall)) {
1521 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1523 $conf->{$opt} = print_lxc_network
($oldnet);
1524 write_config
($vmid, $conf);
1527 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1530 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1534 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1536 my $veth = "veth${vmid}i${netid}";
1537 my $vethpeer = $veth . "p";
1538 my $eth = $newnet->{name
};
1540 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1541 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1543 # attach peer in container
1544 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1545 PVE
::Tools
::run_command
($cmd);
1547 # link up peer in container
1548 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1549 PVE
::Tools
::run_command
($cmd);
1551 my $done = { type
=> 'veth' };
1552 foreach (qw(bridge tag firewall hwaddr name)) {
1553 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1555 $conf->{$opt} = print_lxc_network
($done);
1557 write_config
($vmid, $conf);
1560 sub update_ipconfig
{
1561 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1563 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1565 my $optdata = parse_lxc_network
($conf->{$opt});
1569 my $cmdargs = shift;
1570 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1572 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1574 my $change_ip_config = sub {
1575 my ($ipversion) = @_;
1577 my $family_opt = "-$ipversion";
1578 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1579 my $gw= "gw$suffix";
1580 my $ip= "ip$suffix";
1582 my $newip = $newnet->{$ip};
1583 my $newgw = $newnet->{$gw};
1584 my $oldip = $optdata->{$ip};
1586 my $change_ip = &$safe_string_ne($oldip, $newip);
1587 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1589 return if !$change_ip && !$change_gw;
1591 # step 1: add new IP, if this fails we cancel
1592 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1593 if ($change_ip && $is_real_ip) {
1594 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1601 # step 2: replace gateway
1602 # If this fails we delete the added IP and cancel.
1603 # If it succeeds we save the config and delete the old IP, ignoring
1604 # errors. The config is then saved.
1605 # Note: 'ip route replace' can add
1609 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1610 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1612 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1616 # the route was not replaced, the old IP is still available
1617 # rollback (delete new IP) and cancel
1619 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1620 warn $@ if $@; # no need to die here
1625 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1626 # if the route was not deleted, the guest might have deleted it manually
1632 # from this point on we save the configuration
1633 # step 3: delete old IP ignoring errors
1634 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1635 # We need to enable promote_secondaries, otherwise our newly added
1636 # address will be removed along with the old one.
1639 if ($ipversion == 4) {
1640 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1641 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1642 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1644 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1646 warn $@ if $@; # no need to die here
1648 if ($ipversion == 4) {
1649 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1653 foreach my $property ($ip, $gw) {
1654 if ($newnet->{$property}) {
1655 $optdata->{$property} = $newnet->{$property};
1657 delete $optdata->{$property};
1660 $conf->{$opt} = print_lxc_network
($optdata);
1661 write_config
($vmid, $conf);
1662 $lxc_setup->setup_network($conf);
1665 &$change_ip_config(4);
1666 &$change_ip_config(6);
1670 # Internal snapshots
1672 # NOTE: Snapshot create/delete involves several non-atomic
1673 # action, and can take a long time.
1674 # So we try to avoid locking the file and use 'lock' variable
1675 # inside the config file instead.
1677 my $snapshot_copy_config = sub {
1678 my ($source, $dest) = @_;
1680 foreach my $k (keys %$source) {
1681 next if $k eq 'snapshots';
1682 next if $k eq 'snapstate';
1683 next if $k eq 'snaptime';
1684 next if $k eq 'vmstate';
1685 next if $k eq 'lock';
1686 next if $k eq 'digest';
1687 next if $k eq 'description';
1689 $dest->{$k} = $source->{$k};
1693 my $snapshot_prepare = sub {
1694 my ($vmid, $snapname, $comment) = @_;
1698 my $updatefn = sub {
1700 my $conf = load_config
($vmid);
1702 die "you can't take a snapshot if it's a template\n"
1703 if is_template
($conf);
1707 $conf->{lock} = 'snapshot';
1709 die "snapshot name '$snapname' already used\n"
1710 if defined($conf->{snapshots
}->{$snapname});
1712 my $storecfg = PVE
::Storage
::config
();
1713 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1715 $snap = $conf->{snapshots
}->{$snapname} = {};
1717 &$snapshot_copy_config($conf, $snap);
1719 $snap->{'snapstate'} = "prepare";
1720 $snap->{'snaptime'} = time();
1721 $snap->{'description'} = $comment if $comment;
1722 $conf->{snapshots
}->{$snapname} = $snap;
1724 write_config
($vmid, $conf);
1727 lock_container
($vmid, 10, $updatefn);
1732 my $snapshot_commit = sub {
1733 my ($vmid, $snapname) = @_;
1735 my $updatefn = sub {
1737 my $conf = load_config
($vmid);
1739 die "missing snapshot lock\n"
1740 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1742 die "snapshot '$snapname' does not exist\n"
1743 if !defined($conf->{snapshots
}->{$snapname});
1745 die "wrong snapshot state\n"
1746 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1747 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1749 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1750 delete $conf->{lock};
1751 $conf->{parent
} = $snapname;
1753 write_config
($vmid, $conf);
1756 lock_container
($vmid, 10 ,$updatefn);
1760 my ($feature, $conf, $storecfg, $snapname) = @_;
1764 foreach_mountpoint
($conf, sub {
1765 my ($ms, $mountpoint) = @_;
1767 return if $err; # skip further test
1769 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1771 # TODO: implement support for mountpoints
1772 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1776 return $err ?
0 : 1;
1779 sub snapshot_create
{
1780 my ($vmid, $snapname, $comment) = @_;
1782 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1784 my $conf = load_config
($vmid);
1786 my $running = check_running
($vmid);
1792 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1794 PVE
::Tools
::run_command
(['/bin/sync']);
1797 my $storecfg = PVE
::Storage
::config
();
1798 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1799 my $volid = $rootinfo->{volume
};
1801 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1802 &$snapshot_commit($vmid, $snapname);
1807 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1812 snapshot_delete
($vmid, $snapname, 1);
1817 sub snapshot_delete
{
1818 my ($vmid, $snapname, $force) = @_;
1824 my $updatefn = sub {
1826 $conf = load_config
($vmid);
1828 die "you can't delete a snapshot if vm is a template\n"
1829 if is_template
($conf);
1831 $snap = $conf->{snapshots
}->{$snapname};
1835 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1837 $snap->{snapstate
} = 'delete';
1839 write_config
($vmid, $conf);
1842 lock_container
($vmid, 10, $updatefn);
1844 my $storecfg = PVE
::Storage
::config
();
1846 my $del_snap = sub {
1850 if ($conf->{parent
} eq $snapname) {
1851 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1852 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1854 delete $conf->{parent
};
1858 delete $conf->{snapshots
}->{$snapname};
1860 write_config
($vmid, $conf);
1863 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1864 my $rootinfo = parse_ct_mountpoint
($rootfs);
1865 my $volid = $rootinfo->{volume
};
1868 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1872 if(!$err || ($err && $force)) {
1873 lock_container
($vmid, 10, $del_snap);
1875 die "Can't delete snapshot: $vmid $snapname $err\n";
1880 sub snapshot_rollback
{
1881 my ($vmid, $snapname) = @_;
1883 my $storecfg = PVE
::Storage
::config
();
1885 my $conf = load_config
($vmid);
1887 die "you can't rollback if vm is a template\n" if is_template
($conf);
1889 my $snap = $conf->{snapshots
}->{$snapname};
1891 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1893 my $rootfs = $snap->{rootfs
};
1894 my $rootinfo = parse_ct_mountpoint
($rootfs);
1895 my $volid = $rootinfo->{volume
};
1897 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1899 my $updatefn = sub {
1901 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1902 if $snap->{snapstate
};
1906 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1908 die "unable to rollback vm $vmid: vm is running\n"
1909 if check_running
($vmid);
1911 $conf->{lock} = 'rollback';
1915 # copy snapshot config to current config
1917 my $tmp_conf = $conf;
1918 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1919 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1920 delete $conf->{snaptime
};
1921 delete $conf->{snapname
};
1922 $conf->{parent
} = $snapname;
1924 write_config
($vmid, $conf);
1927 my $unlockfn = sub {
1928 delete $conf->{lock};
1929 write_config
($vmid, $conf);
1932 lock_container
($vmid, 10, $updatefn);
1934 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1936 lock_container
($vmid, 5, $unlockfn);
1939 sub template_create
{
1940 my ($vmid, $conf) = @_;
1942 my $storecfg = PVE
::Storage
::config
();
1944 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1945 my $volid = $rootinfo->{volume
};
1947 die "Template feature is not available for '$volid'\n"
1948 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1950 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1952 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1953 $rootinfo->{volume
} = $template_volid;
1954 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1956 write_config
($vmid, $conf);
1962 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1965 sub mountpoint_names
{
1968 my @names = ('rootfs');
1970 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1971 push @names, "mp$i";
1974 return $reverse ?
reverse @names : @names;
1977 # The container might have *different* symlinks than the host. realpath/abs_path
1978 # use the actual filesystem to resolve links.
1979 sub sanitize_mountpoint
{
1981 $mp = '/' . $mp; # we always start with a slash
1982 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1983 $mp =~ s
@/\./@@g; # collapse /./
1984 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1985 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1986 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1990 sub foreach_mountpoint_full
{
1991 my ($conf, $reverse, $func) = @_;
1993 foreach my $key (mountpoint_names
($reverse)) {
1994 my $value = $conf->{$key};
1995 next if !defined($value);
1996 my $mountpoint = parse_ct_mountpoint
($value, 1);
1997 next if !defined($mountpoint);
1999 # just to be sure: rootfs is /
2000 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
2001 $mountpoint->{mp
} = sanitize_mountpoint
($path);
2003 $path = $mountpoint->{volume
};
2004 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
2006 &$func($key, $mountpoint);
2010 sub foreach_mountpoint
{
2011 my ($conf, $func) = @_;
2013 foreach_mountpoint_full
($conf, 0, $func);
2016 sub foreach_mountpoint_reverse
{
2017 my ($conf, $func) = @_;
2019 foreach_mountpoint_full
($conf, 1, $func);
2022 sub check_ct_modify_config_perm
{
2023 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2025 return 1 if $authuser ne 'root@pam';
2027 foreach my $opt (@$key_list) {
2029 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2030 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2031 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2032 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2033 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2034 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2035 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2036 $opt eq 'searchdomain' || $opt eq 'hostname') {
2037 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2039 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2047 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2049 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2050 my $volid_list = get_vm_volumes
($conf);
2052 foreach_mountpoint_reverse
($conf, sub {
2053 my ($ms, $mountpoint) = @_;
2055 my $volid = $mountpoint->{volume
};
2056 my $mount = $mountpoint->{mp
};
2058 return if !$volid || !$mount;
2060 my $mount_path = "$rootdir/$mount";
2061 $mount_path =~ s!/+!/!g;
2063 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2066 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2079 my ($vmid, $storage_cfg, $conf) = @_;
2081 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2082 File
::Path
::make_path
($rootdir);
2084 my $volid_list = get_vm_volumes
($conf);
2085 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2088 foreach_mountpoint
($conf, sub {
2089 my ($ms, $mountpoint) = @_;
2091 my $volid = $mountpoint->{volume
};
2092 my $mount = $mountpoint->{mp
};
2094 return if !$volid || !$mount;
2096 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2097 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2098 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2100 die "unable to mount base volume - internal error" if $isBase;
2102 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2106 warn "mounting container failed - $err";
2107 umount_all
($vmid, $storage_cfg, $conf, 1);
2114 sub mountpoint_mount_path
{
2115 my ($mountpoint, $storage_cfg, $snapname) = @_;
2117 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2120 my $check_mount_path = sub {
2122 $path = File
::Spec-
>canonpath($path);
2123 my $real = Cwd
::realpath
($path);
2124 if ($real ne $path) {
2125 die "mount path modified by symlink: $path != $real";
2134 if ($line =~ m
@^(/dev/loop\d
+):@) {
2138 my $cmd = ['losetup', '--associated', $path];
2139 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2143 # use $rootdir = undef to just return the corresponding mount path
2144 sub mountpoint_mount
{
2145 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2147 my $volid = $mountpoint->{volume
};
2148 my $mount = $mountpoint->{mp
};
2149 my $type = $mountpoint->{type
};
2151 return if !$volid || !$mount;
2155 if (defined($rootdir)) {
2156 $rootdir =~ s!/+$!!;
2157 $mount_path = "$rootdir/$mount";
2158 $mount_path =~ s!/+!/!g;
2159 &$check_mount_path($mount_path);
2160 File
::Path
::mkpath
($mount_path);
2163 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2165 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2169 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2170 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2172 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2173 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2175 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2177 if ($format eq 'subvol') {
2180 if ($scfg->{type
} eq 'zfspool') {
2181 my $path_arg = $path;
2182 $path_arg =~ s!^/+!!;
2183 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2185 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2188 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2191 return wantarray ?
($path, 0) : $path;
2192 } elsif ($format eq 'raw' || $format eq 'iso') {
2193 my $use_loopdev = 0;
2195 if ($scfg->{path
}) {
2196 push @extra_opts, '-o', 'loop';
2198 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2199 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2202 die "unsupported storage type '$scfg->{type}'\n";
2205 if ($format eq 'iso') {
2206 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2207 } elsif ($isBase || defined($snapname)) {
2208 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2210 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2213 return wantarray ?
($path, $use_loopdev) : $path;
2215 die "unsupported image format '$format'\n";
2217 } elsif ($type eq 'device') {
2218 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2219 return wantarray ?
($volid, 0) : $volid;
2220 } elsif ($type eq 'bind' && -d
$volid) {
2221 &$check_mount_path($volid);
2222 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2223 return wantarray ?
($volid, 0) : $volid;
2226 die "unsupported storage";
2229 sub get_vm_volumes
{
2230 my ($conf, $excludes) = @_;
2234 foreach_mountpoint
($conf, sub {
2235 my ($ms, $mountpoint) = @_;
2237 return if $excludes && $ms eq $excludes;
2239 my $volid = $mountpoint->{volume
};
2241 return if !$volid || $mountpoint->{type
} ne 'volume';
2243 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2246 push @$vollist, $volid;
2253 my ($dev, $rootuid, $rootgid) = @_;
2255 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2256 '-E', "root_owner=$rootuid:$rootgid",
2261 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2263 if ($volid =~ m!^/dev/.+!) {
2268 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2270 die "cannot format volume '$volid' with no storage\n" if !$storage;
2272 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2274 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2276 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2277 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2279 die "cannot format volume '$volid' (format == $format)\n"
2280 if $format ne 'raw';
2282 mkfs
($path, $rootuid, $rootgid);
2286 my ($storecfg, $vollist) = @_;
2288 foreach my $volid (@$vollist) {
2289 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2295 my ($storecfg, $vmid, $settings, $conf) = @_;
2300 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2301 my $chown_vollist = [];
2303 foreach_mountpoint
($settings, sub {
2304 my ($ms, $mountpoint) = @_;
2306 my $volid = $mountpoint->{volume
};
2307 my $mp = $mountpoint->{mp
};
2309 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2311 return if !$storage;
2313 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2314 my ($storeid, $size_gb) = ($1, $2);
2316 my $size_kb = int(${size_gb
}*1024) * 1024;
2318 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2319 # fixme: use better naming ct-$vmid-disk-X.raw?
2321 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2323 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2325 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2327 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2329 push @$chown_vollist, $volid;
2331 } elsif ($scfg->{type
} eq 'zfspool') {
2333 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2335 push @$chown_vollist, $volid;
2336 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2338 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2339 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2341 } elsif ($scfg->{type
} eq 'rbd') {
2343 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2344 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2345 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2347 die "unable to create containers on storage type '$scfg->{type}'\n";
2349 push @$vollist, $volid;
2350 $mountpoint->{volume
} = $volid;
2351 $mountpoint->{size
} = $size_kb * 1024;
2352 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2354 # use specified/existing volid
2358 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2359 foreach my $volid (@$chown_vollist) {
2360 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2361 chown($rootuid, $rootgid, $path);
2363 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2365 # free allocated images on error
2367 destroy_disks
($storecfg, $vollist);
2373 # bash completion helper
2375 sub complete_os_templates
{
2376 my ($cmdname, $pname, $cvalue) = @_;
2378 my $cfg = PVE
::Storage
::config
();
2382 if ($cvalue =~ m/^([^:]+):/) {
2386 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2387 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2390 foreach my $id (keys %$data) {
2391 foreach my $item (@{$data->{$id}}) {
2392 push @$res, $item->{volid
} if defined($item->{volid
});
2399 my $complete_ctid_full = sub {
2402 my $idlist = vmstatus
();
2404 my $active_hash = list_active_containers
();
2408 foreach my $id (keys %$idlist) {
2409 my $d = $idlist->{$id};
2410 if (defined($running)) {
2411 next if $d->{template
};
2412 next if $running && !$active_hash->{$id};
2413 next if !$running && $active_hash->{$id};
2422 return &$complete_ctid_full();
2425 sub complete_ctid_stopped
{
2426 return &$complete_ctid_full(0);
2429 sub complete_ctid_running
{
2430 return &$complete_ctid_full(1);
2440 my $lxc = $conf->{lxc
};
2441 foreach my $entry (@$lxc) {
2442 my ($key, $value) = @$entry;
2443 next if $key ne 'lxc.id_map';
2444 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2445 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2446 push @$id_map, [$type, $ct, $host, $length];
2448 $rootuid = $host if $type eq 'u';
2449 $rootgid = $host if $type eq 'g';
2452 die "failed to parse id_map: $value\n";
2456 if (!@$id_map && $conf->{unprivileged
}) {
2457 # Should we read them from /etc/subuid?
2458 $id_map = [ ['u', '0', '100000', '65536'],
2459 ['g', '0', '100000', '65536'] ];
2460 $rootuid = $rootgid = 100000;
2463 return ($id_map, $rootuid, $rootgid);
2466 sub userns_command
{
2469 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];