12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
21 use Time
::HiRes qw
(gettimeofday
);
25 my $nodename = PVE
::INotify
::nodename
();
27 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
29 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
31 '--xattrs-include=user.*',
32 '--xattrs-include=security.capability',
33 '--warning=no-xattr-write' ];
35 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
41 format_description
=> 'volume',
42 description
=> 'Volume, device or directory to mount into the container.',
46 format_description
=> '[1|0]',
47 description
=> 'Whether to include the mountpoint in backups.',
52 format
=> 'disk-size',
53 format_description
=> 'DiskSize',
54 description
=> 'Volume size (read only value).',
59 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
60 type
=> 'string', format
=> $rootfs_desc,
61 description
=> "Use volume as container root.",
65 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
66 description
=> "The name of the snapshot.",
67 type
=> 'string', format
=> 'pve-configid',
75 description
=> "Lock/unlock the VM.",
76 enum
=> [qw(migrate backup snapshot rollback)],
81 description
=> "Specifies whether a VM will be started during system bootup.",
84 startup
=> get_standard_option
('pve-startup-order'),
88 description
=> "Enable/disable Template.",
94 enum
=> ['amd64', 'i386'],
95 description
=> "OS architecture type.",
101 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
102 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
107 description
=> "Attach a console device (/dev/console) to the container.",
113 description
=> "Specify the number of tty available to the container",
121 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
129 description
=> "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
137 description
=> "Amount of RAM for the VM in MB.",
144 description
=> "Amount of SWAP for the VM in MB.",
150 description
=> "Set a host name for the container.",
151 type
=> 'string', format
=> 'dns-name',
157 description
=> "Container description. Only used on the configuration web interface.",
161 type
=> 'string', format
=> 'dns-name-list',
162 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
166 type
=> 'string', format
=> 'address-list',
167 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
169 rootfs
=> get_standard_option
('pve-ct-rootfs'),
172 type
=> 'string', format
=> 'pve-configid',
174 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
178 description
=> "Timestamp for snapshots.",
184 description
=> "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
186 enum
=> ['shell', 'console', 'tty'],
192 description
=> "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
198 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
203 my $valid_lxc_conf_keys = {
207 'lxc.haltsignal' => 1,
208 'lxc.rebootsignal' => 1,
209 'lxc.stopsignal' => 1,
211 'lxc.network.type' => 1,
212 'lxc.network.flags' => 1,
213 'lxc.network.link' => 1,
214 'lxc.network.mtu' => 1,
215 'lxc.network.name' => 1,
216 'lxc.network.hwaddr' => 1,
217 'lxc.network.ipv4' => 1,
218 'lxc.network.ipv4.gateway' => 1,
219 'lxc.network.ipv6' => 1,
220 'lxc.network.ipv6.gateway' => 1,
221 'lxc.network.script.up' => 1,
222 'lxc.network.script.down' => 1,
224 'lxc.console.logfile' => 1,
227 'lxc.devttydir' => 1,
228 'lxc.hook.autodev' => 1,
232 'lxc.mount.entry' => 1,
233 'lxc.mount.auto' => 1,
235 'lxc.rootfs.mount' => 1,
236 'lxc.rootfs.options' => 1,
240 'lxc.aa_profile' => 1,
241 'lxc.aa_allow_incomplete' => 1,
242 'lxc.se_context' => 1,
245 'lxc.hook.pre-start' => 1,
246 'lxc.hook.pre-mount' => 1,
247 'lxc.hook.mount' => 1,
248 'lxc.hook.start' => 1,
249 'lxc.hook.stop' => 1,
250 'lxc.hook.post-stop' => 1,
251 'lxc.hook.clone' => 1,
252 'lxc.hook.destroy' => 1,
255 'lxc.start.auto' => 1,
256 'lxc.start.delay' => 1,
257 'lxc.start.order' => 1,
259 'lxc.environment' => 1,
270 description
=> "Network interface type.",
275 format_description
=> 'String',
276 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
277 pattern
=> '[-_.\w\d]+',
281 format_description
=> 'vmbr<Number>',
282 description
=> 'Bridge to attach the network device to.',
283 pattern
=> '[-_.\w\d]+',
288 format_description
=> 'MAC',
289 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
290 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
295 format_description
=> 'Number',
296 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
297 minimum
=> 64, # minimum ethernet frame is 64 bytes
302 format
=> 'pve-ipv4-config',
303 format_description
=> 'IPv4Format/CIDR',
304 description
=> 'IPv4 address in CIDR format.',
310 format_description
=> 'GatewayIPv4',
311 description
=> 'Default gateway for IPv4 traffic.',
316 format
=> 'pve-ipv6-config',
317 format_description
=> 'IPv6Format/CIDR',
318 description
=> 'IPv6 address in CIDR format.',
324 format_description
=> 'GatewayIPv6',
325 description
=> 'Default gateway for IPv6 traffic.',
330 format_description
=> '[1|0]',
331 description
=> "Controls whether this interface's firewall rules should be used.",
336 format_description
=> 'VlanNo',
339 description
=> "VLAN tag foro this interface.",
343 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
345 my $MAX_LXC_NETWORKS = 10;
346 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
347 $confdesc->{"net$i"} = {
349 type
=> 'string', format
=> $netconf_desc,
350 description
=> "Specifies network interfaces for the container.",
358 format_description
=> 'Path',
359 description
=> 'Path to the mountpoint as seen from inside the container.',
363 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
367 type
=> 'string', format
=> 'pve-volume-id',
368 description
=> "Reference to unused volumes.",
371 my $MAX_MOUNT_POINTS = 10;
372 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
373 $confdesc->{"mp$i"} = {
375 type
=> 'string', format
=> $mp_desc,
376 description
=> "Use volume as container mount point (experimental feature).",
381 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
382 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
383 $confdesc->{"unused$i"} = $unuseddesc;
386 sub write_pct_config
{
387 my ($filename, $conf) = @_;
389 delete $conf->{snapstate
}; # just to be sure
391 my $generate_raw_config = sub {
396 # add description as comment to top of file
397 my $descr = $conf->{description
} || '';
398 foreach my $cl (split(/\n/, $descr)) {
399 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
402 foreach my $key (sort keys %$conf) {
403 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
404 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
405 my $value = $conf->{$key};
406 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
407 $raw .= "$key: $value\n";
410 if (my $lxcconf = $conf->{lxc
}) {
411 foreach my $entry (@$lxcconf) {
412 my ($k, $v) = @$entry;
420 my $raw = &$generate_raw_config($conf);
422 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
423 $raw .= "\n[$snapname]\n";
424 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
431 my ($key, $value) = @_;
433 die "unknown setting '$key'\n" if !$confdesc->{$key};
435 my $type = $confdesc->{$key}->{type
};
437 if (!defined($value)) {
438 die "got undefined value\n";
441 if ($value =~ m/[\n\r]/) {
442 die "property contains a line feed\n";
445 if ($type eq 'boolean') {
446 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
447 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
448 die "type check ('boolean') failed - got '$value'\n";
449 } elsif ($type eq 'integer') {
450 return int($1) if $value =~ m/^(\d+)$/;
451 die "type check ('integer') failed - got '$value'\n";
452 } elsif ($type eq 'number') {
453 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
454 die "type check ('number') failed - got '$value'\n";
455 } elsif ($type eq 'string') {
456 if (my $fmt = $confdesc->{$key}->{format
}) {
457 PVE
::JSONSchema
::check_format
($fmt, $value);
466 sub parse_pct_config
{
467 my ($filename, $raw) = @_;
469 return undef if !defined($raw);
472 digest
=> Digest
::SHA
::sha1_hex
($raw),
476 $filename =~ m
|/lxc/(\d
+).conf
$|
477 || die "got strange filename '$filename'";
485 my @lines = split(/\n/, $raw);
486 foreach my $line (@lines) {
487 next if $line =~ m/^\s*$/;
489 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
491 $conf->{description
} = $descr if $descr;
493 $conf = $res->{snapshots
}->{$section} = {};
497 if ($line =~ m/^\#(.*)\s*$/) {
498 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
502 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
505 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
506 push @{$conf->{lxc
}}, [$key, $value];
508 warn "vm $vmid - unable to parse config: $line\n";
510 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
511 $descr .= PVE
::Tools
::decode_text
($2);
512 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
513 $conf->{snapstate
} = $1;
514 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
517 eval { $value = check_type
($key, $value); };
518 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
519 $conf->{$key} = $value;
521 warn "vm $vmid - unable to parse config: $line\n";
525 $conf->{description
} = $descr if $descr;
527 delete $res->{snapstate
}; # just to be sure
533 my $vmlist = PVE
::Cluster
::get_vmlist
();
535 return $res if !$vmlist || !$vmlist->{ids
};
536 my $ids = $vmlist->{ids
};
538 foreach my $vmid (keys %$ids) {
539 next if !$vmid; # skip CT0
540 my $d = $ids->{$vmid};
541 next if !$d->{node
} || $d->{node
} ne $nodename;
542 next if !$d->{type
} || $d->{type
} ne 'lxc';
543 $res->{$vmid}->{type
} = 'lxc';
548 sub cfs_config_path
{
549 my ($vmid, $node) = @_;
551 $node = $nodename if !$node;
552 return "nodes/$node/lxc/$vmid.conf";
556 my ($vmid, $node) = @_;
558 my $cfspath = cfs_config_path
($vmid, $node);
559 return "/etc/pve/$cfspath";
563 my ($vmid, $node) = @_;
565 $node = $nodename if !$node;
566 my $cfspath = cfs_config_path
($vmid, $node);
568 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
569 die "container $vmid does not exists\n" if !defined($conf);
575 my ($vmid, $conf) = @_;
577 my $dir = "/etc/pve/nodes/$nodename/lxc";
580 write_config
($vmid, $conf);
586 unlink config_file
($vmid, $nodename);
590 my ($vmid, $conf) = @_;
592 my $cfspath = cfs_config_path
($vmid);
594 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
597 # flock: we use one file handle per process, so lock file
598 # can be called multiple times and succeeds for the same process.
600 my $lock_handles = {};
601 my $lockdir = "/run/lock/lxc";
606 return "$lockdir/pve-config-${vmid}.lock";
610 my ($vmid, $timeout) = @_;
612 $timeout = 10 if !$timeout;
615 my $filename = lock_filename
($vmid);
617 mkdir $lockdir if !-d
$lockdir;
619 my $lock_func = sub {
620 if (!$lock_handles->{$$}->{$filename}) {
621 my $fh = new IO
::File
(">>$filename") ||
622 die "can't open file - $!\n";
623 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
626 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
627 print STDERR
"trying to aquire lock...";
630 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
631 # try again on EINTR (see bug #273)
632 if ($success || ($! != EINTR
)) {
637 print STDERR
" failed\n";
638 die "can't aquire lock - $!\n";
641 print STDERR
" OK\n";
644 $lock_handles->{$$}->{$filename}->{refcount
}++;
647 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
650 die "can't lock file '$filename' - $err";
657 my $filename = lock_filename
($vmid);
659 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
660 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
661 if ($refcount <= 0) {
662 $lock_handles->{$$}->{$filename} = undef;
669 my ($vmid, $timeout, $code, @param) = @_;
673 lock_aquire
($vmid, $timeout);
674 eval { $res = &$code(@param) };
686 return defined($confdesc->{$name});
689 # add JSON properties for create and set function
690 sub json_config_properties
{
693 foreach my $opt (keys %$confdesc) {
694 next if $opt eq 'parent' || $opt eq 'snaptime';
695 next if $prop->{$opt};
696 $prop->{$opt} = $confdesc->{$opt};
702 sub json_config_properties_no_rootfs
{
705 foreach my $opt (keys %$confdesc) {
706 next if $prop->{$opt};
707 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
708 $prop->{$opt} = $confdesc->{$opt};
714 # container status helpers
716 sub list_active_containers
{
718 my $filename = "/proc/net/unix";
720 # similar test is used by lcxcontainers.c: list_active_containers
723 my $fh = IO
::File-
>new ($filename, "r");
726 while (defined(my $line = <$fh>)) {
727 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
729 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
740 # warning: this is slow
744 my $active_hash = list_active_containers
();
746 return 1 if defined($active_hash->{$vmid});
751 sub get_container_disk_usage
{
752 my ($vmid, $pid) = @_;
754 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
757 my $last_proc_vmid_stat;
759 my $parse_cpuacct_stat = sub {
762 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
766 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
779 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
781 my $active_hash = list_active_containers
();
783 my $cpucount = $cpuinfo->{cpus
} || 1;
785 my $cdtime = gettimeofday
;
787 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
789 foreach my $vmid (keys %$list) {
790 my $d = $list->{$vmid};
792 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
793 warn $@ if $@; # ignore errors (consider them stopped)
795 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
797 my $cfspath = cfs_config_path
($vmid);
798 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
800 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
801 $d->{name
} =~ s/[\s]//g;
803 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
806 my $res = get_container_disk_usage
($vmid, $d->{pid
});
807 $d->{disk
} = $res->{used
};
808 $d->{maxdisk
} = $res->{total
};
811 # use 4GB by default ??
812 if (my $rootfs = $conf->{rootfs
}) {
813 my $rootinfo = parse_ct_mountpoint
($rootfs);
814 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
816 $d->{maxdisk
} = 4*1024*1024*1024;
822 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
823 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
834 $d->{template
} = is_template
($conf);
837 foreach my $vmid (keys %$list) {
838 my $d = $list->{$vmid};
841 next if !$pid; # skip stopped CTs
843 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
844 $d->{uptime
} = time - $ctime; # the method lxcfs uses
846 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
847 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
849 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
850 my @bytes = split(/\n/, $blkio_bytes);
851 foreach my $byte (@bytes) {
852 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
853 $d->{diskread
} = $2 if $key eq 'Read';
854 $d->{diskwrite
} = $2 if $key eq 'Write';
858 my $pstat = &$parse_cpuacct_stat($vmid);
860 my $used = $pstat->{utime} + $pstat->{stime
};
862 my $old = $last_proc_vmid_stat->{$vmid};
864 $last_proc_vmid_stat->{$vmid} = {
872 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
875 my $dutime = $used - $old->{used
};
877 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
878 $last_proc_vmid_stat->{$vmid} = {
884 $d->{cpu
} = $old->{cpu
};
888 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
890 foreach my $dev (keys %$netdev) {
891 next if $dev !~ m/^veth([1-9]\d*)i/;
893 my $d = $list->{$vmid};
897 $d->{netout
} += $netdev->{$dev}->{receive
};
898 $d->{netin
} += $netdev->{$dev}->{transmit
};
905 sub parse_ct_mountpoint
{
906 my ($data, $noerr) = @_;
911 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
913 return undef if $noerr;
917 if (defined(my $size = $res->{size
})) {
918 $size = PVE
::JSONSchema
::parse_size
($size);
919 if (!defined($size)) {
920 return undef if $noerr;
921 die "invalid size: $size\n";
923 $res->{size
} = $size;
929 sub print_ct_mountpoint
{
930 my ($info, $nomp) = @_;
931 my $skip = $nomp ?
['mp'] : [];
932 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
935 sub print_lxc_network
{
937 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
940 sub parse_lxc_network
{
945 return $res if !$data;
947 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
949 $res->{type
} = 'veth';
950 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
955 sub read_cgroup_value
{
956 my ($group, $vmid, $name, $full) = @_;
958 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
960 return PVE
::Tools
::file_get_contents
($path) if $full;
962 return PVE
::Tools
::file_read_firstline
($path);
965 sub write_cgroup_value
{
966 my ($group, $vmid, $name, $value) = @_;
968 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
969 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
973 sub find_lxc_console_pids
{
977 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
980 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
983 my @args = split(/\0/, $cmdline);
985 # serach for lxc-console -n <vmid>
986 return if scalar(@args) != 3;
987 return if $args[1] ne '-n';
988 return if $args[2] !~ m/^\d+$/;
989 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
993 push @{$res->{$vmid}}, $pid;
1005 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1007 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1009 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1014 # Note: we cannot use Net:IP, because that only allows strict
1016 sub parse_ipv4_cidr
{
1017 my ($cidr, $noerr) = @_;
1019 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1020 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1023 return undef if $noerr;
1025 die "unable to parse ipv4 address/mask\n";
1031 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1034 sub check_protection
{
1035 my ($vm_conf, $err_msg) = @_;
1037 if ($vm_conf->{protection
}) {
1038 die "$err_msg - protection mode enabled\n";
1042 sub update_lxc_config
{
1043 my ($storage_cfg, $vmid, $conf) = @_;
1045 my $dir = "/var/lib/lxc/$vmid";
1047 if ($conf->{template
}) {
1049 unlink "$dir/config";
1056 die "missing 'arch' - internal error" if !$conf->{arch
};
1057 $raw .= "lxc.arch = $conf->{arch}\n";
1059 my $unprivileged = $conf->{unprivileged
};
1060 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1062 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1063 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1064 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1065 if ($unprivileged || $custom_idmap) {
1066 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1072 $raw .= "lxc.start.unshare = 1\n";
1074 # Should we read them from /etc/subuid?
1075 if ($unprivileged && !$custom_idmap) {
1076 $raw .= "lxc.id_map = u 0 100000 65536\n";
1077 $raw .= "lxc.id_map = g 0 100000 65536\n";
1080 if (!has_dev_console
($conf)) {
1081 $raw .= "lxc.console = none\n";
1082 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1085 my $ttycount = get_tty_count
($conf);
1086 $raw .= "lxc.tty = $ttycount\n";
1088 # some init scripts expects a linux terminal (turnkey).
1089 $raw .= "lxc.environment = TERM=linux\n";
1091 my $utsname = $conf->{hostname
} || "CT$vmid";
1092 $raw .= "lxc.utsname = $utsname\n";
1094 my $memory = $conf->{memory
} || 512;
1095 my $swap = $conf->{swap
} // 0;
1097 my $lxcmem = int($memory*1024*1024);
1098 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1099 $raw .= "lxc.cgroup.memory.kmem.limit_in_bytes = $lxcmem\n";
1101 my $lxcswap = int(($memory + $swap)*1024*1024);
1102 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1104 if (my $cpulimit = $conf->{cpulimit
}) {
1105 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1106 my $value = int(100000*$cpulimit);
1107 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1110 my $shares = $conf->{cpuunits
} || 1024;
1111 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1113 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1114 $mountpoint->{mp
} = '/';
1116 $raw .= "lxc.rootfs = $dir/rootfs\n";
1119 foreach my $k (keys %$conf) {
1120 next if $k !~ m/^net(\d+)$/;
1122 my $d = parse_lxc_network
($conf->{$k});
1124 $raw .= "lxc.network.type = veth\n";
1125 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1126 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1127 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1128 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1131 if (my $lxcconf = $conf->{lxc
}) {
1132 foreach my $entry (@$lxcconf) {
1133 my ($k, $v) = @$entry;
1134 $netcount++ if $k eq 'lxc.network.type';
1135 $raw .= "$k = $v\n";
1139 $raw .= "lxc.network.type = empty\n" if !$netcount;
1141 File
::Path
::mkpath
("$dir/rootfs");
1143 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1146 # verify and cleanup nameserver list (replace \0 with ' ')
1147 sub verify_nameserver_list
{
1148 my ($nameserver_list) = @_;
1151 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1152 PVE
::JSONSchema
::pve_verify_ip
($server);
1153 push @list, $server;
1156 return join(' ', @list);
1159 sub verify_searchdomain_list
{
1160 my ($searchdomain_list) = @_;
1163 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1164 # todo: should we add checks for valid dns domains?
1165 push @list, $server;
1168 return join(' ', @list);
1171 sub add_unused_volume
{
1172 my ($config, $volid) = @_;
1174 # skip bind mounts and block devices
1175 return if $volid =~ m
|^/|;
1178 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1179 my $test = "unused$ind";
1180 if (my $vid = $config->{$test}) {
1181 return if $vid eq $volid; # do not add duplicates
1187 die "To many unused volume - please delete them first.\n" if !$key;
1189 $config->{$key} = $volid;
1194 sub update_pct_config
{
1195 my ($vmid, $conf, $running, $param, $delete) = @_;
1200 my @deleted_volumes;
1204 my $pid = find_lxc_pid
($vmid);
1205 $rootdir = "/proc/$pid/root";
1208 my $hotplug_error = sub {
1210 push @nohotplug, @_;
1217 if (defined($delete)) {
1218 foreach my $opt (@$delete) {
1219 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1220 die "unable to delete required option '$opt'\n";
1221 } elsif ($opt eq 'swap') {
1222 delete $conf->{$opt};
1223 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1224 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1225 delete $conf->{$opt};
1226 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1227 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1228 next if $hotplug_error->($opt);
1229 delete $conf->{$opt};
1230 } elsif ($opt =~ m/^net(\d)$/) {
1231 delete $conf->{$opt};
1234 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1235 } elsif ($opt eq 'protection') {
1236 delete $conf->{$opt};
1237 } elsif ($opt =~ m/^unused(\d+)$/) {
1238 next if $hotplug_error->($opt);
1239 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1240 push @deleted_volumes, $conf->{$opt};
1241 delete $conf->{$opt};
1242 } elsif ($opt =~ m/^mp(\d+)$/) {
1243 next if $hotplug_error->($opt);
1244 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1245 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1246 add_unused_volume
($conf, $mountpoint->{volume
});
1247 delete $conf->{$opt};
1248 } elsif ($opt eq 'unprivileged') {
1249 die "unable to delete read-only option: '$opt'\n";
1253 write_config
($vmid, $conf) if $running;
1257 # There's no separate swap size to configure, there's memory and "total"
1258 # memory (iow. memory+swap). This means we have to change them together.
1259 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1260 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1261 if (defined($wanted_memory) || defined($wanted_swap)) {
1263 $wanted_memory //= ($conf->{memory
} || 512);
1264 $wanted_swap //= ($conf->{swap
} || 0);
1266 my $total = $wanted_memory + $wanted_swap;
1268 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1269 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1271 $conf->{memory
} = $wanted_memory;
1272 $conf->{swap
} = $wanted_swap;
1274 write_config
($vmid, $conf) if $running;
1277 foreach my $opt (keys %$param) {
1278 my $value = $param->{$opt};
1279 if ($opt eq 'hostname') {
1280 $conf->{$opt} = $value;
1281 } elsif ($opt eq 'onboot') {
1282 $conf->{$opt} = $value ?
1 : 0;
1283 } elsif ($opt eq 'startup') {
1284 $conf->{$opt} = $value;
1285 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1286 next if $hotplug_error->($opt);
1287 $conf->{$opt} = $value;
1288 } elsif ($opt eq 'nameserver') {
1289 next if $hotplug_error->($opt);
1290 my $list = verify_nameserver_list
($value);
1291 $conf->{$opt} = $list;
1292 } elsif ($opt eq 'searchdomain') {
1293 next if $hotplug_error->($opt);
1294 my $list = verify_searchdomain_list
($value);
1295 $conf->{$opt} = $list;
1296 } elsif ($opt eq 'cpulimit') {
1297 next if $hotplug_error->($opt); # FIXME: hotplug
1298 $conf->{$opt} = $value;
1299 } elsif ($opt eq 'cpuunits') {
1300 $conf->{$opt} = $value;
1301 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1302 } elsif ($opt eq 'description') {
1303 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1304 } elsif ($opt =~ m/^net(\d+)$/) {
1306 my $net = parse_lxc_network
($value);
1308 $conf->{$opt} = print_lxc_network
($net);
1310 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1312 } elsif ($opt eq 'protection') {
1313 $conf->{$opt} = $value ?
1 : 0;
1314 } elsif ($opt =~ m/^mp(\d+)$/) {
1315 next if $hotplug_error->($opt);
1316 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1317 $conf->{$opt} = $value;
1319 } elsif ($opt eq 'rootfs') {
1320 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1321 die "implement me: $opt";
1322 } elsif ($opt eq 'unprivileged') {
1323 die "unable to modify read-only option: '$opt'\n";
1325 die "implement me: $opt";
1327 write_config
($vmid, $conf) if $running;
1330 if (@deleted_volumes) {
1331 my $storage_cfg = PVE
::Storage
::config
();
1332 foreach my $volume (@deleted_volumes) {
1333 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1338 my $storage_cfg = PVE
::Storage
::config
();
1339 create_disks
($storage_cfg, $vmid, $conf, $conf);
1342 # This should be the last thing we do here
1343 if ($running && scalar(@nohotplug)) {
1344 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1348 sub has_dev_console
{
1351 return !(defined($conf->{console
}) && !$conf->{console
});
1357 return $conf->{tty
} // $confdesc->{tty
}->{default};
1363 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1366 sub get_console_command
{
1367 my ($vmid, $conf) = @_;
1369 my $cmode = get_cmode
($conf);
1371 if ($cmode eq 'console') {
1372 return ['lxc-console', '-n', $vmid, '-t', 0];
1373 } elsif ($cmode eq 'tty') {
1374 return ['lxc-console', '-n', $vmid];
1375 } elsif ($cmode eq 'shell') {
1376 return ['lxc-attach', '--clear-env', '-n', $vmid];
1378 die "internal error";
1382 sub get_primary_ips
{
1385 # return data from net0
1387 return undef if !defined($conf->{net0
});
1388 my $net = parse_lxc_network
($conf->{net0
});
1390 my $ipv4 = $net->{ip
};
1392 if ($ipv4 =~ /^(dhcp|manual)$/) {
1398 my $ipv6 = $net->{ip6
};
1400 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1407 return ($ipv4, $ipv6);
1410 sub delete_mountpoint_volume
{
1411 my ($storage_cfg, $vmid, $volume) = @_;
1413 # skip bind mounts and block devices
1414 if ($volume =~ m
|^/|) {
1418 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1419 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1422 sub destroy_lxc_container
{
1423 my ($storage_cfg, $vmid, $conf) = @_;
1425 foreach_mountpoint
($conf, sub {
1426 my ($ms, $mountpoint) = @_;
1427 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1430 rmdir "/var/lib/lxc/$vmid/rootfs";
1431 unlink "/var/lib/lxc/$vmid/config";
1432 rmdir "/var/lib/lxc/$vmid";
1433 destroy_config
($vmid);
1435 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1436 #PVE::Tools::run_command($cmd);
1439 sub vm_stop_cleanup
{
1440 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1445 my $vollist = get_vm_volumes
($conf);
1446 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1449 warn $@ if $@; # avoid errors - just warn
1452 my $safe_num_ne = sub {
1455 return 0 if !defined($a) && !defined($b);
1456 return 1 if !defined($a);
1457 return 1 if !defined($b);
1462 my $safe_string_ne = sub {
1465 return 0 if !defined($a) && !defined($b);
1466 return 1 if !defined($a);
1467 return 1 if !defined($b);
1473 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1475 if ($newnet->{type
} ne 'veth') {
1476 # for when there are physical interfaces
1477 die "cannot update interface of type $newnet->{type}";
1480 my $veth = "veth${vmid}i${netid}";
1481 my $eth = $newnet->{name
};
1483 if (my $oldnetcfg = $conf->{$opt}) {
1484 my $oldnet = parse_lxc_network
($oldnetcfg);
1486 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1487 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1489 PVE
::Network
::veth_delete
($veth);
1490 delete $conf->{$opt};
1491 write_config
($vmid, $conf);
1493 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1495 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1496 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1497 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1499 if ($oldnet->{bridge
}) {
1500 PVE
::Network
::tap_unplug
($veth);
1501 foreach (qw(bridge tag firewall)) {
1502 delete $oldnet->{$_};
1504 $conf->{$opt} = print_lxc_network
($oldnet);
1505 write_config
($vmid, $conf);
1508 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1509 foreach (qw(bridge tag firewall)) {
1510 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1512 $conf->{$opt} = print_lxc_network
($oldnet);
1513 write_config
($vmid, $conf);
1516 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1519 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1523 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1525 my $veth = "veth${vmid}i${netid}";
1526 my $vethpeer = $veth . "p";
1527 my $eth = $newnet->{name
};
1529 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1530 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1532 # attach peer in container
1533 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1534 PVE
::Tools
::run_command
($cmd);
1536 # link up peer in container
1537 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1538 PVE
::Tools
::run_command
($cmd);
1540 my $done = { type
=> 'veth' };
1541 foreach (qw(bridge tag firewall hwaddr name)) {
1542 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1544 $conf->{$opt} = print_lxc_network
($done);
1546 write_config
($vmid, $conf);
1549 sub update_ipconfig
{
1550 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1552 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1554 my $optdata = parse_lxc_network
($conf->{$opt});
1558 my $cmdargs = shift;
1559 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1561 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1563 my $change_ip_config = sub {
1564 my ($ipversion) = @_;
1566 my $family_opt = "-$ipversion";
1567 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1568 my $gw= "gw$suffix";
1569 my $ip= "ip$suffix";
1571 my $newip = $newnet->{$ip};
1572 my $newgw = $newnet->{$gw};
1573 my $oldip = $optdata->{$ip};
1575 my $change_ip = &$safe_string_ne($oldip, $newip);
1576 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1578 return if !$change_ip && !$change_gw;
1580 # step 1: add new IP, if this fails we cancel
1581 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1582 if ($change_ip && $is_real_ip) {
1583 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1590 # step 2: replace gateway
1591 # If this fails we delete the added IP and cancel.
1592 # If it succeeds we save the config and delete the old IP, ignoring
1593 # errors. The config is then saved.
1594 # Note: 'ip route replace' can add
1598 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1599 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1601 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1605 # the route was not replaced, the old IP is still available
1606 # rollback (delete new IP) and cancel
1608 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1609 warn $@ if $@; # no need to die here
1614 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1615 # if the route was not deleted, the guest might have deleted it manually
1621 # from this point on we save the configuration
1622 # step 3: delete old IP ignoring errors
1623 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1624 # We need to enable promote_secondaries, otherwise our newly added
1625 # address will be removed along with the old one.
1628 if ($ipversion == 4) {
1629 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1630 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1631 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1633 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1635 warn $@ if $@; # no need to die here
1637 if ($ipversion == 4) {
1638 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1642 foreach my $property ($ip, $gw) {
1643 if ($newnet->{$property}) {
1644 $optdata->{$property} = $newnet->{$property};
1646 delete $optdata->{$property};
1649 $conf->{$opt} = print_lxc_network
($optdata);
1650 write_config
($vmid, $conf);
1651 $lxc_setup->setup_network($conf);
1654 &$change_ip_config(4);
1655 &$change_ip_config(6);
1659 # Internal snapshots
1661 # NOTE: Snapshot create/delete involves several non-atomic
1662 # action, and can take a long time.
1663 # So we try to avoid locking the file and use 'lock' variable
1664 # inside the config file instead.
1666 my $snapshot_copy_config = sub {
1667 my ($source, $dest) = @_;
1669 foreach my $k (keys %$source) {
1670 next if $k eq 'snapshots';
1671 next if $k eq 'snapstate';
1672 next if $k eq 'snaptime';
1673 next if $k eq 'vmstate';
1674 next if $k eq 'lock';
1675 next if $k eq 'digest';
1676 next if $k eq 'description';
1678 $dest->{$k} = $source->{$k};
1682 my $snapshot_prepare = sub {
1683 my ($vmid, $snapname, $comment) = @_;
1687 my $updatefn = sub {
1689 my $conf = load_config
($vmid);
1691 die "you can't take a snapshot if it's a template\n"
1692 if is_template
($conf);
1696 $conf->{lock} = 'snapshot';
1698 die "snapshot name '$snapname' already used\n"
1699 if defined($conf->{snapshots
}->{$snapname});
1701 my $storecfg = PVE
::Storage
::config
();
1702 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1704 $snap = $conf->{snapshots
}->{$snapname} = {};
1706 &$snapshot_copy_config($conf, $snap);
1708 $snap->{'snapstate'} = "prepare";
1709 $snap->{'snaptime'} = time();
1710 $snap->{'description'} = $comment if $comment;
1711 $conf->{snapshots
}->{$snapname} = $snap;
1713 write_config
($vmid, $conf);
1716 lock_container
($vmid, 10, $updatefn);
1721 my $snapshot_commit = sub {
1722 my ($vmid, $snapname) = @_;
1724 my $updatefn = sub {
1726 my $conf = load_config
($vmid);
1728 die "missing snapshot lock\n"
1729 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1731 die "snapshot '$snapname' does not exist\n"
1732 if !defined($conf->{snapshots
}->{$snapname});
1734 die "wrong snapshot state\n"
1735 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1736 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1738 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1739 delete $conf->{lock};
1740 $conf->{parent
} = $snapname;
1742 write_config
($vmid, $conf);
1745 lock_container
($vmid, 10 ,$updatefn);
1749 my ($feature, $conf, $storecfg, $snapname) = @_;
1753 foreach_mountpoint
($conf, sub {
1754 my ($ms, $mountpoint) = @_;
1756 return if $err; # skip further test
1758 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1760 # TODO: implement support for mountpoints
1761 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1765 return $err ?
0 : 1;
1768 sub snapshot_create
{
1769 my ($vmid, $snapname, $comment) = @_;
1771 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1773 my $conf = load_config
($vmid);
1775 my $running = check_running
($vmid);
1778 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1779 PVE
::Tools
::run_command
(['/bin/sync']);
1782 my $storecfg = PVE
::Storage
::config
();
1783 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1784 my $volid = $rootinfo->{volume
};
1787 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1790 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1791 &$snapshot_commit($vmid, $snapname);
1794 snapshot_delete
($vmid, $snapname, 1);
1799 sub snapshot_delete
{
1800 my ($vmid, $snapname, $force) = @_;
1806 my $updatefn = sub {
1808 $conf = load_config
($vmid);
1810 die "you can't delete a snapshot if vm is a template\n"
1811 if is_template
($conf);
1813 $snap = $conf->{snapshots
}->{$snapname};
1817 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1819 $snap->{snapstate
} = 'delete';
1821 write_config
($vmid, $conf);
1824 lock_container
($vmid, 10, $updatefn);
1826 my $storecfg = PVE
::Storage
::config
();
1828 my $del_snap = sub {
1832 if ($conf->{parent
} eq $snapname) {
1833 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1834 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1836 delete $conf->{parent
};
1840 delete $conf->{snapshots
}->{$snapname};
1842 write_config
($vmid, $conf);
1845 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1846 my $rootinfo = parse_ct_mountpoint
($rootfs);
1847 my $volid = $rootinfo->{volume
};
1850 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1854 if(!$err || ($err && $force)) {
1855 lock_container
($vmid, 10, $del_snap);
1857 die "Can't delete snapshot: $vmid $snapname $err\n";
1862 sub snapshot_rollback
{
1863 my ($vmid, $snapname) = @_;
1865 my $storecfg = PVE
::Storage
::config
();
1867 my $conf = load_config
($vmid);
1869 die "you can't rollback if vm is a template\n" if is_template
($conf);
1871 my $snap = $conf->{snapshots
}->{$snapname};
1873 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1875 my $rootfs = $snap->{rootfs
};
1876 my $rootinfo = parse_ct_mountpoint
($rootfs);
1877 my $volid = $rootinfo->{volume
};
1879 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1881 my $updatefn = sub {
1883 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1884 if $snap->{snapstate
};
1888 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1890 die "unable to rollback vm $vmid: vm is running\n"
1891 if check_running
($vmid);
1893 $conf->{lock} = 'rollback';
1897 # copy snapshot config to current config
1899 my $tmp_conf = $conf;
1900 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1901 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1902 delete $conf->{snaptime
};
1903 delete $conf->{snapname
};
1904 $conf->{parent
} = $snapname;
1906 write_config
($vmid, $conf);
1909 my $unlockfn = sub {
1910 delete $conf->{lock};
1911 write_config
($vmid, $conf);
1914 lock_container
($vmid, 10, $updatefn);
1916 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1918 lock_container
($vmid, 5, $unlockfn);
1921 sub template_create
{
1922 my ($vmid, $conf) = @_;
1924 my $storecfg = PVE
::Storage
::config
();
1926 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1927 my $volid = $rootinfo->{volume
};
1929 die "Template feature is not available for '$volid'\n"
1930 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1932 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1934 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1935 $rootinfo->{volume
} = $template_volid;
1936 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1938 write_config
($vmid, $conf);
1944 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1947 sub mountpoint_names
{
1950 my @names = ('rootfs');
1952 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1953 push @names, "mp$i";
1956 return $reverse ?
reverse @names : @names;
1959 # The container might have *different* symlinks than the host. realpath/abs_path
1960 # use the actual filesystem to resolve links.
1961 sub sanitize_mountpoint
{
1963 $mp = '/' . $mp; # we always start with a slash
1964 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1965 $mp =~ s
@/\./@@g; # collapse /./
1966 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1967 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1968 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1972 sub foreach_mountpoint_full
{
1973 my ($conf, $reverse, $func) = @_;
1975 foreach my $key (mountpoint_names
($reverse)) {
1976 my $value = $conf->{$key};
1977 next if !defined($value);
1978 my $mountpoint = parse_ct_mountpoint
($value, 1);
1979 next if !defined($mountpoint);
1981 # just to be sure: rootfs is /
1982 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1983 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1985 $path = $mountpoint->{volume
};
1986 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1988 &$func($key, $mountpoint);
1992 sub foreach_mountpoint
{
1993 my ($conf, $func) = @_;
1995 foreach_mountpoint_full
($conf, 0, $func);
1998 sub foreach_mountpoint_reverse
{
1999 my ($conf, $func) = @_;
2001 foreach_mountpoint_full
($conf, 1, $func);
2004 sub check_ct_modify_config_perm
{
2005 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2007 return 1 if $authuser ne 'root@pam';
2009 foreach my $opt (@$key_list) {
2011 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2012 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2013 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2014 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2015 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2016 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2017 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2018 $opt eq 'searchdomain' || $opt eq 'hostname') {
2019 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2021 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2029 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2031 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2032 my $volid_list = get_vm_volumes
($conf);
2034 foreach_mountpoint_reverse
($conf, sub {
2035 my ($ms, $mountpoint) = @_;
2037 my $volid = $mountpoint->{volume
};
2038 my $mount = $mountpoint->{mp
};
2040 return if !$volid || !$mount;
2042 my $mount_path = "$rootdir/$mount";
2043 $mount_path =~ s!/+!/!g;
2045 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2048 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2061 my ($vmid, $storage_cfg, $conf) = @_;
2063 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2064 File
::Path
::make_path
($rootdir);
2066 my $volid_list = get_vm_volumes
($conf);
2067 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2070 foreach_mountpoint
($conf, sub {
2071 my ($ms, $mountpoint) = @_;
2073 my $volid = $mountpoint->{volume
};
2074 my $mount = $mountpoint->{mp
};
2076 return if !$volid || !$mount;
2078 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2079 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2080 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2082 die "unable to mount base volume - internal error" if $isBase;
2084 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2088 warn "mounting container failed - $err";
2089 umount_all
($vmid, $storage_cfg, $conf, 1);
2096 sub mountpoint_mount_path
{
2097 my ($mountpoint, $storage_cfg, $snapname) = @_;
2099 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2102 my $check_mount_path = sub {
2104 $path = File
::Spec-
>canonpath($path);
2105 my $real = Cwd
::realpath
($path);
2106 if ($real ne $path) {
2107 die "mount path modified by symlink: $path != $real";
2111 # use $rootdir = undef to just return the corresponding mount path
2112 sub mountpoint_mount
{
2113 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2115 my $volid = $mountpoint->{volume
};
2116 my $mount = $mountpoint->{mp
};
2118 return if !$volid || !$mount;
2122 if (defined($rootdir)) {
2123 $rootdir =~ s!/+$!!;
2124 $mount_path = "$rootdir/$mount";
2125 $mount_path =~ s!/+!/!g;
2126 &$check_mount_path($mount_path);
2127 File
::Path
::mkpath
($mount_path);
2130 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2132 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2136 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2137 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2139 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2140 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2142 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2144 if ($format eq 'subvol') {
2147 if ($scfg->{type
} ne 'zfspool') {
2148 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2151 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2153 return wantarray ?
($path, 0) : $path;
2154 } elsif ($format eq 'raw' || $format eq 'iso') {
2155 my $use_loopdev = 0;
2157 if ($scfg->{path
}) {
2158 push @extra_opts, '-o', 'loop';
2160 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2163 die "unsupported storage type '$scfg->{type}'\n";
2166 if ($format eq 'iso') {
2167 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2168 } elsif ($isBase || defined($snapname)) {
2169 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2171 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2174 return wantarray ?
($path, $use_loopdev) : $path;
2176 die "unsupported image format '$format'\n";
2178 } elsif ($volid =~ m
|^/dev/.+|) {
2179 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2180 return wantarray ?
($volid, 0) : $volid;
2181 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2182 &$check_mount_path($volid);
2183 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2184 return wantarray ?
($volid, 0) : $volid;
2187 die "unsupported storage";
2190 sub get_vm_volumes
{
2191 my ($conf, $excludes) = @_;
2195 foreach_mountpoint
($conf, sub {
2196 my ($ms, $mountpoint) = @_;
2198 return if $excludes && $ms eq $excludes;
2200 my $volid = $mountpoint->{volume
};
2202 return if !$volid || $volid =~ m
|^/|;
2204 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2207 push @$vollist, $volid;
2214 my ($dev, $rootuid, $rootgid) = @_;
2216 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2217 '-E', "root_owner=$rootuid:$rootgid",
2222 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2224 if ($volid =~ m!^/dev/.+!) {
2229 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2231 die "cannot format volume '$volid' with no storage\n" if !$storage;
2233 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2235 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2237 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2238 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2240 die "cannot format volume '$volid' (format == $format)\n"
2241 if $format ne 'raw';
2243 mkfs
($path, $rootuid, $rootgid);
2247 my ($storecfg, $vollist) = @_;
2249 foreach my $volid (@$vollist) {
2250 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2256 my ($storecfg, $vmid, $settings, $conf) = @_;
2261 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2262 my $chown_vollist = [];
2264 foreach_mountpoint
($settings, sub {
2265 my ($ms, $mountpoint) = @_;
2267 my $volid = $mountpoint->{volume
};
2268 my $mp = $mountpoint->{mp
};
2270 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2272 return if !$storage;
2274 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2275 my ($storeid, $size_gb) = ($1, $2);
2277 my $size_kb = int(${size_gb
}*1024) * 1024;
2279 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2280 # fixme: use better naming ct-$vmid-disk-X.raw?
2282 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2284 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2286 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2288 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2290 push @$chown_vollist, $volid;
2292 } elsif ($scfg->{type
} eq 'zfspool') {
2294 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2296 push @$chown_vollist, $volid;
2297 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2299 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2300 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2302 } elsif ($scfg->{type
} eq 'rbd') {
2304 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2305 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2306 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2308 die "unable to create containers on storage type '$scfg->{type}'\n";
2310 push @$vollist, $volid;
2311 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2312 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2314 # use specified/existing volid
2318 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2319 foreach my $volid (@$chown_vollist) {
2320 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2321 chown($rootuid, $rootgid, $path);
2323 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2325 # free allocated images on error
2327 destroy_disks
($storecfg, $vollist);
2333 # bash completion helper
2335 sub complete_os_templates
{
2336 my ($cmdname, $pname, $cvalue) = @_;
2338 my $cfg = PVE
::Storage
::config
();
2342 if ($cvalue =~ m/^([^:]+):/) {
2346 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2347 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2350 foreach my $id (keys %$data) {
2351 foreach my $item (@{$data->{$id}}) {
2352 push @$res, $item->{volid
} if defined($item->{volid
});
2359 my $complete_ctid_full = sub {
2362 my $idlist = vmstatus
();
2364 my $active_hash = list_active_containers
();
2368 foreach my $id (keys %$idlist) {
2369 my $d = $idlist->{$id};
2370 if (defined($running)) {
2371 next if $d->{template
};
2372 next if $running && !$active_hash->{$id};
2373 next if !$running && $active_hash->{$id};
2382 return &$complete_ctid_full();
2385 sub complete_ctid_stopped
{
2386 return &$complete_ctid_full(0);
2389 sub complete_ctid_running
{
2390 return &$complete_ctid_full(1);
2400 my $lxc = $conf->{lxc
};
2401 foreach my $entry (@$lxc) {
2402 my ($key, $value) = @$entry;
2403 next if $key ne 'lxc.id_map';
2404 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2405 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2406 push @$id_map, [$type, $ct, $host, $length];
2408 $rootuid = $host if $type eq 'u';
2409 $rootgid = $host if $type eq 'g';
2412 die "failed to parse id_map: $value\n";
2416 if (!@$id_map && $conf->{unprivileged
}) {
2417 # Should we read them from /etc/subuid?
2418 $id_map = [ ['u', '0', '100000', '65536'],
2419 ['g', '0', '100000', '65536'] ];
2420 $rootuid = $rootgid = 100000;
2423 return ($id_map, $rootuid, $rootgid);
2426 sub userns_command
{
2429 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];